<template>
  <v-stage ref="stage" @click="stageClick" @contextmenu="contextmenuClick" @mousedown="stageMouseDown" @mousemove="stageMouseMove" @mouseup="stageMouseUp" @dblclick="stageDblclick" />
</template>

<script>
import { parse } from 'svg-parser'
import FontData from '@/components/TextFontElement/fontData'
import TemplateConst from '@/design/templateconst'
import Fontfaceobserver from 'fontfaceobserver'
import { v4 as uuidv4 } from 'uuid'
import { getCenter, getRotateAroundPoint, getScaleAroundCenter, getFlipXY, newLocation, getFitScale, getFillScale } from '@/konva/postool'
import stageTool from '@/konva/stagetool'
import debounce from 'lodash.debounce'
import Konva from 'konva'
import { fullUrl, imgPorxy } from '@/api/path'
import store from '@/store'
import { toTemplateNode, getFonts, reverseHorizontal, getPx, getMM, isCommonMaskLayer } from '@/design/templatetool'
import { initHistory, initHistoryData, setHistoryData, setHistoryCompareData } from '@/konva/historytool'
import { circle, grid } from '@/konva/pathtool'
import { bisSuccessMsg, bisErrorMsg } from '@/api/utils/message'
import UserMixin from '@/mixin/userMixin.js'
import FrameEdit from '@/components/FrameEdit/main.js'
import NoEdit from '@/components/No/index.js' // 修改No
import QRcodeEdit from '@/components/QRcode/index.js' // 修改二维码
import TextEdit from '@/components/TextEdit/index.js' // 修改文字
import gridEdit from '@/components/GridDialog/index.js' // 修改表格
import { post } from '@/api/utils/apiwrapper' // 请求api
// konva shape name中的用处：frame 是一个相框，nonTransform不可扭曲只能截取，同时支持边框。FitScale只能等比例变形
export default {
  name: 'MainKonva',
  components: { },
  mixins: [UserMixin],
  props: {},
  data() {
    return {
      // showCum: false,//无用到，暂时注释掉
      trConfigArray: [], // 存儲變形前的值
      dragConfigArray: [] // 存儲拖拽前的值
    }
  },
  computed: {},
  created() {
  },
  methods: {
    getTransformerNode() {
      return this.getKonvaNode(TemplateConst.TRANSFORMER_ID)
    },
    getSelectRectNode() {
      return this.getKonvaNode(TemplateConst.SELECT_RECT_ID)
    },
    getMaskLayerNode() {
      return this.getKonvaNode(TemplateConst.MASKLAYER_ID)
    },
    getEditLayerNode() {
      return this.getKonvaNode(TemplateConst.EDITLAYER_ID)
    },
    getBackgroundLayerNode() {
      return this.getKonvaNode(TemplateConst.BACKGROUNDLAYER_ID)
    },
    getContentAreaLineNode() {
      return this.getKonvaNode(TemplateConst.CONTENTAREALINE_ID)
    },
    getBgFillNode() {
      return this.getKonvaNode(TemplateConst.BGFILL_PATH_ID)
    },
    getKonvaNode(id) {
      return stageTool.getKonvaNode(this.$refs.stage.getStage(), id)
    },
    moveToCenter(shape) {
      var contentNode = this.getContentAreaLineNode()
      this.moveToCenterByParent(shape, contentNode)
    },
    clearTransformer() {
      this.getTransformerNode().nodes([])// 取消transformer
      this.editAttrs()
    },
    // 此方法要尽量保持简洁，简单
    stageClick(e) { // 画布上面所有点击事件都在此处理，包括设计元素的点击事件
    // down-click-up 先触发clickdown 再出发click-最后触发clickup
      var selectRect = this.getSelectRectNode()
      if (selectRect.visible() && selectRect.startPos) { // 在选择画框中，无需再进行处理,由stageMouseUp进行处理
        return
      }
      const stage = this.$refs.stage.getStage()
      if (e.target === stage) { // 点击画布空白处
        this.clearTransformer()// 取消选择框
        return
      }
      this.movePosition(e.target) // 获取坐标信息
      if (!e.target.hasName('design')) { // 点击的不是可编辑设计元素
        return
      }

      // do we pressed shift or ctrl?
      const tr = this.getTransformerNode()

      const metaPressed = e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey// 按住的键

      if (e.target.hasName('Fit')) { // 打孔和压线，只支持单选
        if (!metaPressed || tr.nodes().length === 0) { // 已选择为空，或者单选时进行选中
          this.tranformerSelected([e.target])// 单选
        }
        return
      }

      if (metaPressed && tr.nodes()[0] && tr.nodes()[0].hasName('Fit')) { // 多选情况下，打孔和压线，只支持单选不允许加入
        return
      }
      var targetNode = e.target
      // if (targetNode.hasName('group') && targetNode.hasName('bg')) { // group群组的事件监听node
      //   targetNode = targetNode.getParent()
      // }
      while (targetNode.getParent().getClassName() === 'Group') { // 如果是群组下的元素，必须选择群组
        targetNode = targetNode.getParent()
      }

      const isSelected = tr.nodes().indexOf(targetNode) >= 0// 是否已经在选择框里
      if (!metaPressed && !isSelected) { // 不是在多选情况下，并且之前没有选中过
        // if no key pressed and the node is not selected
        // select just one
        // while (topElement.getParent().getClassName() === 'Group') {
        //   topElement = topElement.getParent()
        // }
        tr.nodes([targetNode])// 单选
      } else if (metaPressed && isSelected) { // 在多选情况下，当前shape已被选中了，进行取消选择
        // if we pressed keys and node was selected
        // we need to remove it from selection:
        const nodes = tr.nodes().slice() // use slice to have new copy of array
        // remove node from array
        nodes.splice(nodes.indexOf(targetNode), 1)
        tr.nodes(nodes)// 取消选择
      } else if (metaPressed && !isSelected) { // 在多选情况下，当前shape未选中，进行追加选择
        // add the node into selection
        // if (!e.target.hasName('Fit') && tr.nodes()[0] && !tr.nodes()[0].hasName('Fit')) { // 需過濾掉打孔與壓線，!tr.nodes()[0].hasName('Fit')代表已选择了fit不能再选择其他
        // while (topElement.getParent().getClassName() === 'Group') {
        //   topElement = topElement.getParent()
        // }
        const nodes = tr.nodes().concat(targetNode)
        tr.nodes(nodes)// 多选
        // }
      }
      this.editAttrs()// 激活工具条还有对应的激活功能
    },
    stageDblclick(e) { // 双击编辑设计元素(双击事件都会先触发stageClick点击事件)
      if (!e.target.hasName('design')) { // 点击的不是可编辑设计元素
        return
      }
      if (e.target.hasName('nonTransform')) { // 点击相框
        var bgFill = this.getBgFillNode()
        FrameEdit.show(e.target.getClassName(), Konva.Util.cloneObject(e.target.getAttrs()), { fillWidth: bgFill.width(), fillHeight: bgFill.height() }, (shape) => {
          const targetRect = e.target.getSelfRect()
          const shapeRect = shape.getSelfRect()
          const scale = Math.max(targetRect.width / shapeRect.width, targetRect.height / shapeRect.height)// 计算一个合适的尺寸放置。铺满原始框最大的一边
          if (typeof (shape.radius) === 'function') {
            e.target.radius(shape.radius())
          }
          // 由于相框要保存scale为1，这样边框就不会有缩放的问题
          e.target.scaleX(scale)
          e.target.scaleY(scale)
          e.target.width(shape.width())
          e.target.height(shape.height())
          // e.target.fill(null)
          e.target.fillPatternImage(shape.fillPatternImage())
          e.target.fillPatternX(shape.fillPatternX())
          e.target.fillPatternY(shape.fillPatternY())
          e.target.fillPatternScaleX(shape.fillPatternScaleX())
          e.target.fillPatternScaleY(shape.fillPatternScaleY())
          e.target.fillPatternRotation(shape.fillPatternRotation())
          e.target.fillPatternRepeat(shape.fillPatternRepeat())
          e.target.setAttrs({
            'srcFillPatternItem': shape.getAttr('srcFillPatternItem'),
            'stroke': shape.getAttr('stroke'),
            'strokeWidth': shape.getAttr('strokeWidth')
          })

          stageTool.reSetScaleToOne(e.target)
          this.editAttrs()
          // const size = stageTool.getSize(e.target)
          // var width = Math.max(size.width * e.target.scaleX(), 10)// 恢复成1:1
          // var height = Math.max(size.height * e.target.scaleY(), 10)// 恢复成1:1
          // const oldFillTransform = stageTool.getFillTransform(e.target)
          // const delaFillTransform = new Konva.Transform()// 新的变化
          // delaFillTransform.scale(width / size.width, height / size.height)// 新增加缩放
          // const newFillTransform = new Konva.Transform()
          // newFillTransform.multiply(delaFillTransform).multiply(oldFillTransform)
          // stageTool.setFillTransform(e.target, newFillTransform)
          // stageTool.setSize(e.target, { width: width, height: height })
          // e.target.setAttrs({ // 为了照顾边框不被缩放，采用scale不缩放的方式
          //   scaleX: 1,
          //   scaleY: 1
          // })
        })
        return
      }
      // 双击二维码
      if (e.target.hasName('qrcode')) {
        QRcodeEdit.edit({ ...e.target.getAttrs() }, (item, data) => {
          stageTool.getImage(uuidv4(), item.url).then(function({ id, img }) {
            e.target.setAttrs({
              'fillPatternImage': img,
              'srcQrData': data
            })
          })
        })
      }
      if (e.target.getParent().getClassName() === 'Group') { // 已经被群组了的shape不允许进行编辑，需要解除群组才能编辑
        return
      }
      if (e.target.hasName('NoText')) {
        this.NoTextdbClick(e)
        return
      }
      // 双击表格
      if (e.target.hasName('Grid')) {
        gridEdit.edit(e.target.attrs.srcGridData, (data) => {
          e.target.clearCache()
          data.col = Number(data.col)
          data.row = Number(data.row)
          data.strokeWidth = Number(data.strokeWidth)
          const pathdata = grid(0, 0, e.target.attrs.width, e.target.attrs.height, data.col, data.row)
          var newData = {
            data: pathdata,
            srcGridData: data,
            fill: data.fill,
            stroke: data.stroke,
            strokeWidth: data.strokeWidth
          }
          this.setAttrs(newData)
          e.target.cache()
        })
        return
      }
      switch (e.target.className) {
        case 'TextPath':// 双击文字元素处理
          TextEdit.edit(e.target.getAttr('text'), e.target.getAttr('data'), (value, d) => {
            var data = { text: value, data: d }
            this.setAttrs(data)
            store.commit('template/updateEditNode', { pageId: store.state.design.pageId, dataArray: [{ 'id': e.target.getAttr('id'), 'value': data }] })
          })

          break
        case 'Text':
          TextEdit.edit(e.target.getAttr('text'), '', (value, d) => {
            if (value && value !== '') {
              var data = { text: value, width: 'auto', height: 'auto' }// 重置长宽范围,解决由于拉伸框的时候，尺寸不够，文字被遮住的问题
              this.setAttrs(data)
              store.commit('template/updateEditNode', { pageId: store.state.design.pageId, dataArray: [{ 'id': e.target.getAttr('id'), 'value': data }] })
            }
          })
          break
      }
      if (TemplateConst.CRIMPLINE_NAME === e.target.getName() || TemplateConst.NEEDLELINE_NAME === e.target.getName()) { // 壓線、針線雙擊處理
        this.lineDbclick(e)
      }
    },
    stageMouseDown(e) {
      var menuNode = document.getElementById('contextmenu')
      menuNode.style.display = 'none'
      // stage任意位置点击都会触发事件
      const stage = this.$refs.stage.getStage()
      if (e.target.hasName('design')) {
        var tr = this.getTransformerNode()
        const metaPressed = e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey// 按住的键

        var targetNode = e.target
        while (targetNode.getParent().getClassName() === 'Group') { // 如果是群组下的元素，必须选择群组,目前对于design群组的都是选中到群组，不选择群组里面的元素
          targetNode = targetNode.getParent()
        }

        const isSelected = tr.nodes().indexOf(targetNode) >= 0
        if (!metaPressed && !isSelected) { // 沒有按住也沒有選中，才添加(只处理单选过程，多选过程在click事件中处理)
          this.tranformerSelected([targetNode])
          return
        }
      }
      if (TemplateConst.CRIMPLINE_NAME === e.target.getName() || TemplateConst.NEEDLELINE_NAME === e.target.getName() || TemplateConst.FOLDING_NAME === e.target.getName()) { // 壓線、針線、摺工MouseDown處理
        this.lineDragmove(e)
      }
      if (e.target !== stage) { // 后续代码只处理点击画布的事件。用来画选择范围框
        return
      }
      var selectRect = this.getSelectRectNode()
      if (selectRect.visible() && selectRect.startPos) { // 已经正在画框中，无需再进行处理
        return
      }

      // 点击的是画布空白区,当作stageClick事件来处理
      e.evt.preventDefault()// 阻止画布的其他默认行为
      // 只有点击画布才开始做选择框，点击了其他shape是对独立shape的处理
      // 开始画选择框
      selectRect.startPos = { x: stage.getRelativePointerPosition().x, y: stage.getRelativePointerPosition().y } // 临时存放原始开始左边
      selectRect.setAttrs({
        x: stage.getRelativePointerPosition().x,
        y: stage.getRelativePointerPosition().y,
        width: 0,
        height: 0,
        visible: true
      })
      this.clearTransformer()// 取消transformer
    },
    contextmenuClick(e) {
      const tr = this.getTransformerNode()
      this.$emit('nodeLength', tr.nodes().length)
      e.evt.preventDefault()
      var menuNode = document.getElementById('contextmenu')
      menuNode.style.display = 'initial'
      menuNode.style.top = e.evt.clientY + 'px'
      menuNode.style.left = e.evt.clientX + 'px'
    },
    stageMouseMove(e) {
      var selectRect = this.getSelectRectNode()
      if (selectRect && (!selectRect.visible() || !selectRect.startPos)) { // 只处理在画布画选择框的情形
        return
      }
      e.evt.preventDefault()
      const stage = this.$refs.stage.getStage()
      if (selectRect) {
        selectRect.setAttrs({
          x: Math.min(selectRect.startPos.x, stage.getRelativePointerPosition().x),
          y: Math.min(selectRect.startPos.y, stage.getRelativePointerPosition().y),
          width: Math.abs(selectRect.startPos.x - stage.getRelativePointerPosition().x),
          height: Math.abs(selectRect.startPos.y - stage.getRelativePointerPosition().y)
        })
      }
    },
    stageMouseUp(e) {
      // 只处理画布画框多选的情况
      // 处理多选
      var selectRect = this.getSelectRectNode()
      if (TemplateConst.CRIMPLINE_NAME === e.target.getName() || TemplateConst.NEEDLELINE_NAME === e.target.getName() || TemplateConst.FOLDING_NAME === e.target.getName()) { // 壓線、針線、摺工MouseUp處理
        this.lineDragend()
      }
      if (!selectRect.visible()) { // 没有在画布画选择框
        return
      }
      e.evt.preventDefault()
      selectRect.visible(false)
      const box = selectRect.getClientRect()

      var selected = this.getEditLayerNode().children.filter((shape) => Konva.Util.haveIntersection(box, shape.getClientRect()) && shape.hasName('design') &&
        (typeof (shape.getAttrs().visible) === 'undefined' || shape.getAttrs().visible) && // 隱藏的過濾掉
        (typeof (shape.getAttrs().listening) === 'undefined' || shape.getAttrs().listening) // 鎖定的過濾掉
      )
      this.getTransformerNode().nodes(selected)
      this.editAttrs()
    },
    tranformerSelected(nodes) {
      this.getTransformerNode().nodes(nodes)
      this.editAttrs()
    },
    editAttrs() { // 激活编辑toolbar
      const tr = this.getTransformerNode()
      if (tr.nodes().length === 0) {
        this.$emit('actattrs', { activate: '' })
        this.$emit('unActivateLayer', { pageId: store.state.design.pageId }) // 取消激活左側圖層元素
        return
      }

      if (tr.nodes().length === 1 && tr.nodes()[0]) { // 单选
        const name = tr.nodes()[0].getAttr('name')
        if (name === 'design Fit Punch' || name === TemplateConst.CRIMPLINE_NAME || name === TemplateConst.NEEDLELINE_NAME) {
          this.$emit('unActivateLayer', { pageId: store.state.design.pageId }) // 點擊打孔或壓線元素，取消激活左側圖層元素
        }
      }

      var arr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]// 0 text（选中文本的个数） count,1 ill（插图的个数） count,2 img（上传文件个数） count,3 element（元素个数） count,4 group（群组） count,5 fit（固定的后加工） count,6 clip（遮罩） count，7 QRcode（二维码） count，8 FitScale(只能等比例缩放) count, 9 nonTransform(圖片相框) count, 10 frame(相框) count
      tr.nodes().forEach(shape => {
        if (shape.hasName('design')) {
          if (shape.hasName('frame')) { // 有相框，左側導航欄切換上傳
            this.$emit('changeTab', 'Upload')
          }
          if (shape.hasName('Fit')) { // 固定元素，不可以变更，拉伸，旋转
            arr[5]++
          }
          if (shape.hasName('FitScale')) { // 可以等比例拉伸，但不能变形(圆形相框和qrcode的node都加入了此名称)
            arr[8]++
          }
          if (shape.hasName('previewbox')) { // 预览框
            arr[12]++
          }
          if (shape.hasName('text') || shape.hasName('textPath')) {
            arr[0]++
          } else if (shape.hasName('ill') && !shape.hasName('nonTransform') && !shape.hasName('frame')) {
            arr[1]++
          } else if (shape.hasName('image') && !shape.hasName('nonTransform') && !shape.hasName('frame')) {
            arr[2]++
          } else if (shape.hasName('element')) {
            arr[3]++
          } else if (shape.hasName('group')) {
            arr[4]++
          } else if (shape.hasName('clip')) { // 遮罩
            arr[6]++
          } else if (shape.hasName('QRcode')) { // 二维码
            arr[7]++
          } else if (shape.hasName('nonTransform')) { // 圖片相框
            arr[9]++
          } else if (shape.hasName('frame')) { // 相框
            arr[10]++
          } else if (shape.hasName('Path')) { // svg
            arr[11]++
          } else if (shape.hasName('CircleText')) {
            arr[13]++
          }
        }
      })
      var activate = 'base'
      if (arr[0] > 0 && arr[1] === 0 && arr[2] === 0 && arr[3] === 0 && arr[4] === 0 && arr[6] === 0 && arr[7] === 0 && arr[9] === 0 && arr[10] === 0) {
        activate = 'text'
      } else if (arr[0] === 0 && arr[1] > 0 && arr[2] === 0 && arr[3] === 0 && arr[4] === 0 && arr[6] === 0 && arr[7] === 0 && arr[9] === 0 && arr[10] === 0) {
        activate = 'ill'
      } else if (arr[0] === 0 && arr[1] === 0 && arr[2] > 0 && arr[3] === 0 && arr[4] === 0 && arr[6] === 0 && arr[7] === 0 && arr[9] === 0 && arr[10] === 0) {
        activate = 'image'
      } else if (arr[0] === 0 && arr[1] === 0 && arr[2] === 0 && arr[3] > 0 && arr[4] === 0 && arr[6] === 0 && arr[7] === 0 && arr[9] === 0 && arr[10] === 0) {
        activate = 'element'
      } else if (arr[0] === 0 && arr[1] === 0 && arr[2] === 0 && arr[3] === 0 && arr[4] > 0 && arr[6] === 0 && arr[7] === 0 && arr[9] === 0 && arr[10] === 0) {
        activate = 'group'
      } else if (arr[0] === 0 && arr[1] === 0 && arr[2] === 0 && arr[3] === 0 && arr[4] === 0 && arr[6] > 0 && arr[7] === 0 && arr[9] === 0 && arr[10] === 0) {
        activate = 'unClip' // 取消遮罩
      } else if (arr[0] === 0 && arr[1] === 0 && arr[2] === 0 && arr[3] === 0 && arr[4] === 0 && arr[6] === 0 && arr[7] > 0 && arr[9] === 0 && arr[10] === 0) {
        activate = 'QRcode'
      } else if (arr[0] === 0 && arr[1] === 0 && arr[2] === 0 && arr[3] === 0 && arr[4] === 0 && arr[6] === 0 && arr[7] === 0 && arr[9] > 0 && arr[10] === 0) {
        activate = 'nonTransform'
      } else if (arr[0] === 0 && arr[1] === 0 && arr[2] === 0 && arr[3] === 0 && arr[4] === 0 && arr[6] === 0 && arr[7] === 0 && arr[9] === 0 && arr[10] > 0) {
        activate = 'frame'
      } else if (arr[0] === 0 && arr[1] === 0 && arr[2] === 0 && arr[3] === 0 && arr[4] === 0 && arr[6] === 0 && arr[7] === 0 && arr[9] === 0 && arr[10] === 0 && arr[11] > 0) {
        activate = 'Path'
      } else if (arr[0] === 0 && arr[1] === 0 && arr[2] === 0 && arr[3] === 0 && arr[4] === 0 && arr[6] === 0 && arr[7] === 0 && arr[9] === 0 && arr[10] === 0 && arr[11] === 0 && arr[12] > 0) {
        activate = 'previewbox'
      } else if (arr[0] === 0 && arr[1] === 0 && arr[2] === 0 && arr[3] === 0 && arr[4] === 0 && arr[6] === 0 && arr[7] === 0 && arr[9] === 0 && arr[10] === 0 && arr[11] === 0 && arr[12] === 0 && arr[13] > 0) {
        activate = 'CircleText'
      }
      var len = arr.filter(i => i > 0).length
      if (len > 1 && arr[3] > 0 && arr[9] > 0 && arr[4] === 0 && arr[6] === 0) { // 遮罩物 + 圖片相框 + 排除group + 排除遮罩
        activate = 'clip' // 開啓遮罩
      } else if (len > 1 && arr[5] === 0 && arr[8] === 0) {
        activate = 'mix' // 混合
      }
      tr.resizeEnabled(true)
      tr.rotateEnabled(true)
      if (arr[5] > 0) { // 有Fit不变形
        tr.resizeEnabled(false)
        tr.rotateEnabled(false)
      }
      if (arr[12] > 0) {
        tr.rotateEnabled(false)
      }

      if (arr[8] > 0) { // 不显示上下左右
        tr.enabledAnchors(['top-left', 'top-right', 'bottom-left', 'bottom-right'])
      } else {
        tr.enabledAnchors(['top-left', 'top-center', 'top-right', 'middle-right', 'middle-left', 'bottom-left', 'bottom-center', 'bottom-right'])
      }
      this.$emit('actattrs', { activate: activate, attrs: activate === '' ? '' : tr.nodes()[0].getAttrs() })
      this.$emit('activateLayer', { pageId: store.state.design.pageId, konvaId: tr.nodes()[0].getAttr('id') }) // 激活左側圖層元素
    },
    addUndo(history, clearRedoFlag) {
      if (history && history.length > 0) {
        store.commit('history/pushUndoHistory', { pageId: store.state.design.pageId, history: history })
        // 当前页已被编辑过
        store.commit('design/dirty', true)
        this.debounceSavePage(true, store.state.design.pageId)
      }
      if (clearRedoFlag && store.getters.redoHistory.length > 0) { // 是否清空redo
        store.commit('history/clearRedoHistory', store.state.design.pageId)
      }
    },
    addRedo(history) {
      if (history && history.length > 0) {
        store.commit('history/pushRedoHistory', { pageId: store.state.design.pageId, history: history })
        // 当前页已被编辑过
        store.commit('design/dirty', true)
        this.debounceSavePage(true, store.state.design.pageId)
      }
    },
    initTrDragDataCommon() { // 獲取變形、拖拽前的值
      var dataArray = []
      this.getTransformerNode().getNodes().forEach(shape => {
        if (shape.hasName('design')) {
          dataArray.push(setHistoryData(shape.id(), 'attrs', shape.getAttrs(), shape.getAttrs()))
        }
      })
      return dataArray
    },
    trDragHistoryCommon(configArray) { // 獲取變形、拖拽的history
      var sourceHistory = []
      if (configArray.length > 0) {
        this.getTransformerNode().getNodes().forEach(shape => {
          if (shape.hasName('design')) {
            var arr = configArray.find(item => item.id === shape.id())
            if (arr) {
              var historyData = setHistoryCompareData(shape.id(), 'attrs', shape.getAttrs(), arr.data)
              if (historyData != null) {
                sourceHistory.push(historyData)
              }
            }
          }
        })
      }
      return sourceHistory
    },
    handleTransformstart({ evt: e, target: node }) {
      // 清除svg缓存
      if (node.hasName('Path')) {
        node.clearCache()
      }
      this.trConfigArray = this.initTrDragDataCommon() // 存儲變形前的值
    },
    handleTransformend({ evt: e, target: node }) {
      this.movePosition(node) // 获取坐标信息
      var sourceHistory = this.trDragHistoryCommon(this.trConfigArray)
      this.addUndo(sourceHistory, true)
      this.editAttrs()
      if (node.hasName('Path')) {
        node.setAttrs({
          width: node.attrs.width,
          height: node.attrs.height
        })
        node.cache()
      }
    },
    handleTrDragstart(e) {
      var isFoldingDrag = false
      e.target.nodes().forEach(node => {
        if (TemplateConst.FOLDING_NAME === node.getName()) {
          isFoldingDrag = true
        }
      })
      if (isFoldingDrag) { // 排除摺工
        return
      }
      this.dragConfigArray = this.initTrDragDataCommon() // 存儲拖拽前的值
    },
    handleTrDragend(e) {
      var isFoldingDrag = false
      e.target.nodes().forEach(node => {
        if (TemplateConst.FOLDING_NAME === node.getName()) {
          isFoldingDrag = true
        }
      })
      if (isFoldingDrag) { // 排除摺工
        return
      }
      var sourceHistory = this.trDragHistoryCommon(this.dragConfigArray)
      this.addUndo(sourceHistory, true)
      this.editAttrs()
    },
    // 顯示隱藏打印區域和畫布區域
    changeGuidelines() {
      stageTool.seteGuidelines(this.$refs.stage.getStage(), store.state.design.guidelines)
      store.commit('design/guidelines', !store.state.design.guidelines)
    },
    // 以下方法发生动作 history变化
    // 打孔后加工，punch={px:xxx,mm:xxx,dpi:xxx}
    addPunch(punch) {
      punch.id = uuidv4()
      var config = {
        width: punch.px,
        height: punch.px,
        strokeWidth: 1,
        name: 'design Fit Punch',
        fill: TemplateConst.DEFAULT_ELEMENT_FILL,
        stroke: TemplateConst.DEFAULT_ELEMENT_STROKE,
        id: punch.id,
        x: 0,
        y: 0,
        draggable: true,
        dashEnabled: false,
        shadowEnabled: false,
        strokeScaleEnabled: false,
        mm: punch.mm
      }
      const NodeClass = Konva['Circle']
      const shape = new NodeClass(config)
      this.moveToCenter(shape)

      this.getMaskLayerNode().add(shape)
      shape.moveToBottom()
      this.tranformerSelected([shape])
      store.commit('template/addPunch', punch)
      this.addUndo(initHistory(shape.id(), 'destroy', { 'className': shape.getClassName(), 'konva': shape.getAttrs() }), true)
    },
    // Pad后加工
    addPad(direction) {
      // 获取全部的子节点 如果有后加工就删除
      this.getMaskLayerNode().children.forEach((item) => {
        if (item.getAttr('name') === 'design Fit Punch ProcessingLeft' || item.getAttr('name') === 'design Fit Punch processing' || item.getAttr('name') === 'design Fit group ProcessingLine' || item.getAttr('name') === 'design Fit group processing') {
          this.delete([item])
        }
      })
      store.commit('template/deleteProcessing') // 清空
      let width = getPx(8, store.state.template.dpi) // 左边宽度
      let height = getPx(8, store.state.template.dpi) // 上边的高度
      const lineX = getPx(15, store.state.template.dpi)
      const lineY = getPx(15, store.state.template.dpi)
      let x = 0
      const { reverseH } = store.getters.maskType(store.state.design.pageId) // 判断正反面
      if (reverseH) {
        // 反面
        x = store.state.template.width
      }
      if (direction.pad === 'pad-left') {
        height = store.state.template.height
        var config = {
          width,
          height,
          strokeWidth: 0,
          name: 'design Fit Punch ProcessingLeft',
          fill: '#5ec5f3',
          stroke: TemplateConst.DEFAULT_ELEMENT_STROKE,
          id: uuidv4(),
          x: x === 0 ? 70 : x + 70 - width,
          y: 70,
          radius: 40
        }
        const NodeClass = Konva['Rect']
        const shape = new NodeClass(config)
        this.getMaskLayerNode().add(shape)
        shape.moveToBottom()
        this.tranformerSelected([shape])
        //
        this.addUndo(initHistory(shape.id(), 'destroy', { 'className': shape.getClassName(), 'konva': shape.getAttrs() }), true)
      } else if (direction.pad === 'pad-top') {
        width = store.state.template.width
        var config2 = {
          width,
          height,
          strokeWidth: 0,
          name: 'design Fit Punch processing',
          fill: '#5ec5f3',
          stroke: TemplateConst.DEFAULT_ELEMENT_STROKE,
          id: uuidv4(),
          x: 70,
          y: 70,
          draggable: false,
          dashEnabled: false,
          shadowEnabled: false,
          strokeScaleEnabled: false
        }
        const NodeClass = Konva['Rect']
        const shape = new NodeClass(config2)

        this.getMaskLayerNode().add(shape)
        shape.moveToBottom()
        this.tranformerSelected([shape])
        // store.commit('template/addPunch', punch)
        this.addUndo(initHistory(shape.id(), 'destroy', { 'className': shape.getClassName(), 'konva': shape.getAttrs() }), true)
      } else if (direction.pad === 'padLine-left') {
        height = store.state.template.height
        // 群组
        var group = new Konva.Group({
          x: x === 0 ? 70 : x + 70 - width,
          y: 70,
          width,
          height,
          id: uuidv4(),
          name: 'design Fit group ProcessingLine'
        })
        // 矩形
        const config = {
          width,
          height,
          strokeWidth: 0,
          name: 'design ',
          fill: '#5ec5f3',
          id: uuidv4(),
          x: 0,
          y: 0
          // draggable: false,
        }
        const NodeClass = Konva['Rect']
        const shape = new NodeClass(config)
        group.add(shape)
        // 小白点的配置
        const sRect = [
          {
            x: width / 2,
            y: height / 3,
            width: 10,
            height: 50
          }, {
            x: width / 2,
            y: height / 1.5,
            width: 10,
            height: 50
          }
        ]
        // 添加小白点的函数
        this.addWhiteRect(group, sRect)
        // 虚线
        // 虚线配置
        const lineConfig = {
          points: [0, 0, 0, height],
          y: 0,
          x: x === 0 ? lineX : width - lineX
        }
        this.addLine(group, lineConfig)

        this.getMaskLayerNode().add(group)
        group.moveToBottom()
        this.tranformerSelected([group])
        // store.commit('template/addProcessing', group)
        this.addUndo(initHistory(group.id(), 'destroy', { 'className': group.getClassName(), 'konva': group.getAttrs() }), true)
      } else if (direction.pad === 'padLine-top') {
        width = store.state.template.width
        const group = new Konva.Group({
          x: 70,
          y: 70,
          width,
          height,
          id: uuidv4(),
          name: 'design Fit group processing'
          // draggable: false,
        })

        const config = {
          width,
          height,
          strokeWidth: 0,
          name: 'design ',
          fill: '#5ec5f3',
          id: uuidv4(),
          x: 0,
          y: 0
          // draggable: false,
        }
        const NodeClass = Konva['Rect']
        const shape = new NodeClass(config)
        group.add(shape)
        // 小白点的配置
        const sRect = [
          {
            x: width / 3,
            y: height / 2,
            width: 50,
            height: 10
          }, {
            x: width / 1.5,
            y: height / 2,
            width: 50,
            height: 10
          }
        ]
        // 添加小白点的函数
        this.addWhiteRect(group, sRect)
        // 虚线配置
        const lineConfig = {
          points: [0, 0, width, 0],
          y: lineY,
          x: 0
        }
        this.addLine(group, lineConfig)
        this.getMaskLayerNode().add(group)
        group.moveToBottom()
        this.tranformerSelected([group])
        // store.commit('template/addProcessing', group)
        this.addUndo(initHistory(group.id(), 'destroy', { 'className': group.getClassName(), 'konva': group.getAttrs() }), true)
      }
      store.commit('template/addProcessing', { direction, num: 1 })
    },
    // Pad后加工两个白点
    addWhiteRect(group, config) {
      for (let i = 0; i < config.length; i++) {
        const config2 = {
          width: config[i].width,
          height: config[i].height,
          strokeWidth: 0,
          name: 'design ',
          fill: '#fff',
          id: uuidv4(),
          x: config[i].x,
          y: config[i].y,
          draggable: false,
          dashEnabled: false,
          shadowEnabled: false,
          strokeScaleEnabled: false
        }
        const NodeClass2 = Konva['Rect']
        const shape2 = new NodeClass2(config2)
        group.add(shape2)
      }
    },
    // Pad后加工虚线
    addLine(group, config) {
      const LineConfig = {
        points: config.points,
        name: 'design',
        fill: TemplateConst.DEFAULT_ELEMENT_FILL,
        stroke: '#fc0808',
        strokeWidth: 0.8,
        id: uuidv4(),
        x: config.x,
        y: config.y,
        draggable: false,
        shadowEnabled: false,
        strokeScaleEnabled: false,
        dash: [TemplateConst.CRIMPLINE_DASH, TemplateConst.CRIMPLINE_DASH],
        lineJoin: 'round'
        // direction: TemplateConst.LINE_DIRECTION_H
      }
      const LineClass = Konva['Line']
      const Line = new LineClass(LineConfig)
      group.add(Line)
    },
    // 壓線后加工，crimpLine={direction:xxx}
    addCrimpLine(crimpLine) {
      crimpLine.id = uuidv4()
      const stage = this.$refs.stage.getStage()
      const scale = stage.getScaleX()
      var config = {}
      var commonConfig = {
        id: crimpLine.id,
        x: 0,
        y: 0,
        name: TemplateConst.CRIMPLINE_NAME,
        fill: TemplateConst.DEFAULT_ELEMENT_FILL,
        stroke: TemplateConst.DEFAULT_ELEMENT_STROKE,
        strokeWidth: TemplateConst.CRIMPLINE_STROKEWIDTH,
        hitStrokeWidth: TemplateConst.LINE_HITSTROKEWIDTH,
        draggable: true,
        shadowEnabled: false,
        strokeScaleEnabled: false,
        dash: [TemplateConst.CRIMPLINE_DASH, TemplateConst.CRIMPLINE_DASH],
        lineJoin: 'round'
      }
      if (crimpLine.direction === TemplateConst.LINE_DIRECTION_H) { // 橫線horizontal
        config = {
          ...commonConfig,
          points: [0, 0, stage.width() / scale, 0],
          direction: TemplateConst.LINE_DIRECTION_H,
          dragBoundFunc: function(pos) {
            return {
              x: this.absolutePosition().x,
              y: pos.y
            }
          }
        }
      } else { // 豎線vertical
        config = {
          ...commonConfig,
          points: [0, 0, 0, stage.height() / scale],
          direction: TemplateConst.LINE_DIRECTION_V,
          dragBoundFunc: function(pos) {
            return {
              x: pos.x,
              y: this.absolutePosition().y
            }
          }
        }
      }
      const NodeClass = Konva['Line']
      const shape = new NodeClass(config)
      this.moveToCenter(shape)

      this.getMaskLayerNode().add(shape)
      shape.moveToBottom()
      this.tranformerSelected([shape])
      store.commit('template/addCrimpLine', crimpLine)
      this.addUndo(initHistory(shape.id(), 'destroy', { 'className': shape.getClassName(), 'konva': shape.getAttrs() }), true)
    },
    // 針線后加工，needleLine={direction:xxx}
    addNeedleLine(needleLine) {
      needleLine.id = uuidv4()
      const stage = this.$refs.stage.getStage()
      const scale = stage.getScaleX()
      var config = {}
      var commonConfig = {
        id: needleLine.id,
        x: 0,
        y: 0,
        name: TemplateConst.NEEDLELINE_NAME,
        fill: TemplateConst.DEFAULT_ELEMENT_FILL,
        stroke: TemplateConst.DEFAULT_ELEMENT_STROKE,
        strokeWidth: TemplateConst.NEEDLELINE_STROKEWIDTH,
        hitStrokeWidth: TemplateConst.LINE_HITSTROKEWIDTH,
        draggable: true,
        shadowEnabled: false,
        strokeScaleEnabled: false,
        dash: [TemplateConst.NEEDLELINE_DASH, TemplateConst.NEEDLELINE_DASH],
        lineJoin: 'round'
      }
      if (needleLine.direction === TemplateConst.LINE_DIRECTION_H) { // 橫線horizontal
        config = {
          ...commonConfig,
          points: [0, 0, stage.width() / scale, 0],
          direction: TemplateConst.LINE_DIRECTION_H,
          dragBoundFunc: function(pos) {
            return {
              x: this.absolutePosition().x,
              y: pos.y
            }
          }
        }
      } else { // 豎線vertical
        config = {
          ...commonConfig,
          points: [0, 0, 0, stage.height() / scale],
          direction: TemplateConst.LINE_DIRECTION_V,
          dragBoundFunc: function(pos) {
            return {
              x: pos.x,
              y: this.absolutePosition().y
            }
          }
        }
      }
      const NodeClass = Konva['Line']
      const shape = new NodeClass(config)
      this.moveToCenter(shape)

      this.getMaskLayerNode().add(shape)
      shape.moveToBottom()
      this.tranformerSelected([shape])
      store.commit('template/addNeedleLine', needleLine)
      this.addUndo(initHistory(shape.id(), 'destroy', { 'className': shape.getClassName(), 'konva': shape.getAttrs() }), true)
    },
    // 壓線、針線位置
    linePos(e) {
      var linePos = 0
      var direction = e.target.attrs.direction
      if (direction === TemplateConst.LINE_DIRECTION_H) { // 橫
        var excludeHeightPx = store.state.template.maskPadding[0] + store.state.template.bleedPadding[0] // mask位 + 出血位
        var heightPx = e.target.getY() - excludeHeightPx
        linePos = Math.round(getMM(heightPx, store.state.template.dpi) * 10) / 10 // round四舍五入
      } else if (direction === TemplateConst.LINE_DIRECTION_V) { // 豎
        var excludeWidthPx = store.state.template.maskPadding[2] + store.state.template.bleedPadding[2] // mask位 + 出血位
        var widthPx = e.target.getX() - excludeWidthPx
        linePos = Math.round(getMM(widthPx, store.state.template.dpi) * 10) / 10 // round四舍五入
      }
      return parseInt(linePos) + 0.5 <= linePos ? parseInt(linePos) + 0.5 : parseInt(linePos)
    },
    // 壓線、針線、摺工拖動事件
    lineDragmove(e) {
      var tooltipPos = {} // 提示位置
      var typeName = ''
      var name = e.target.attrs.name
      var direction = e.target.attrs.direction
      if (TemplateConst.CRIMPLINE_NAME === name || TemplateConst.NEEDLELINE_NAME === name) { // 壓線、針線
        if (direction === TemplateConst.LINE_DIRECTION_H) { // 橫
          var excludeHeightPx = store.state.template.maskPadding[0] + store.state.template.bleedPadding[0] // mask位 + 出血位
          var heightPx = store.state.template.height // 成品尺寸高
          var y = e.target.getY()
          if (y < excludeHeightPx) { // 限制拖動範圍
            e.target.setAttrs({
              y: excludeHeightPx
            })
          } else if (y > excludeHeightPx + heightPx) {
            e.target.setAttrs({
              y: excludeHeightPx + heightPx
            })
          }
          tooltipPos = { x: e.target.getWidth() / 2, y: e.target.getY() + 5 }
        } else if (direction === TemplateConst.LINE_DIRECTION_V) { // 豎
          var excludeWidthPx = store.state.template.maskPadding[2] + store.state.template.bleedPadding[2] // mask位 + 出血位
          var widthPx = store.state.template.width // 成品尺寸寬
          var x = e.target.getX()
          if (x < excludeWidthPx) { // 限制拖動範圍
            e.target.setAttrs({
              x: excludeWidthPx
            })
          } else if (x > excludeWidthPx + widthPx) {
            e.target.setAttrs({
              x: excludeWidthPx + widthPx
            })
          }
          tooltipPos = { x: e.target.getX() + 5, y: e.target.getHeight() / 2 }
        }
      }
      var tooltip = this.getKonvaNode(TemplateConst.TOOLTIP_ID)
      var tooltipLayer = this.getKonvaNode(TemplateConst.TOOLTIPLAYER_ID)
      if (TemplateConst.CRIMPLINE_NAME === name) {
        typeName = '壓線:' + this.linePos(e) + 'mm'
      } else if (TemplateConst.NEEDLELINE_NAME === name) {
        typeName = '針線:' + this.linePos(e) + 'mm'
      } else if (TemplateConst.FOLDING_NAME === name) {
        typeName = '摺工'
        var srcFoldId = e.target.attrs.srcFoldId
        var points = e.target.attrs.points
        if (srcFoldId === TemplateConst.FOLDING_ID_DUIZHE) { // 對摺
          tooltipPos = { x: points[2] / 2, y: points[1] }
        } else if (srcFoldId === TemplateConst.FOLDING_ID_FENGQINZHE2TIAOGU) { // 風琴摺2條骨
          tooltipPos = { x: points[0], y: points[3] / 2 }
        } else if (srcFoldId === TemplateConst.FOLDING_ID_HEBAOZHE) { // 荷包摺
          tooltipPos = { x: points[2] / 2, y: points[1] }
        }
      }
      tooltip.text(typeName)
      tooltip.position(tooltipPos)
      tooltip.show()
      tooltipLayer.batchDraw()
    },
    // 壓線、針線、摺工拖動完成事件
    lineDragend() {
      var tooltip = this.getKonvaNode(TemplateConst.TOOLTIP_ID)
      var tooltipLayer = this.getKonvaNode(TemplateConst.TOOLTIPLAYER_ID)
      tooltip.hide()
      tooltipLayer.draw()
    },
    // 壓線、針線雙擊事件
    lineDbclick(e) {
      var direction = e.target.attrs.direction
      var linePos = this.linePos(e)
      this.$prompt('', '編輯線位置(單位mm)', {
        closeOnClickModal: false,
        customClass: 'message-text',
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        inputType: 'textarea',
        inputPlaceholder: '請輸入數字',
        showClose: false,
        roundButton: true,
        inputValue: linePos
      }).then(({ value }) => {
        var reg = /^\d+$/ // 整數
        if (value && value !== '' && reg.test(value)) {
          var oldAttrs = {} // 改變前的屬性
          var oldValuePx = getPx(linePos, store.state.template.dpi)
          var valuePx = getPx(value, store.state.template.dpi)
          var includeHeightPx = store.state.template.maskPadding[0] + store.state.template.bleedPadding[0] // mask位 + 出血位
          var includeWidthPx = store.state.template.maskPadding[2] + store.state.template.bleedPadding[2] // mask位 + 出血位
          if (direction === TemplateConst.LINE_DIRECTION_H) { // 橫
            if (value < 0 || value > store.state.template.originHeight) {
              return bisErrorMsg('請輸入正確的範圍！')
            }
            e.target.setAttrs({
              y: includeHeightPx + valuePx
            })
            oldAttrs = { y: includeHeightPx + oldValuePx }
          } else { // 豎
            if (value < 0 || value > store.state.template.originWidth) {
              return bisErrorMsg('請輸入正確的範圍！')
            }
            e.target.setAttrs({
              x: includeWidthPx + valuePx
            })
            oldAttrs = { x: includeWidthPx + oldValuePx }
          }
          this.addUndo(initHistory(e.target.getId(), 'crimpOrNeedleLine', { 'direction': direction, 'konva': oldAttrs }), true)
        } else {
          return bisErrorMsg('請輸入整數！')
        }
      })
    },
    // 摺工后加工，folding={srcFoldId:xxx, text:xxx}
    addFolding(folding) {
      folding.ids = []
      const stage = this.$refs.stage.getStage()
      const stageWidth = stage.width() / stage.getScaleX() // 舞臺寬高
      const stageHeight = stage.height() / stage.getScaleX()
      const width = store.state.template.width // 成品尺寸寬高
      const height = store.state.template.height
      const topHeightPx = store.state.template.maskPadding[0] + store.state.template.bleedPadding[0] // mask位 + 出血位
      const leftWidthPx = store.state.template.maskPadding[2] + store.state.template.bleedPadding[2]
      var configArray = []
      var config = {
        x: 0,
        y: 0,
        name: TemplateConst.FOLDING_NAME,
        fill: TemplateConst.DEFAULT_ELEMENT_FILL,
        stroke: TemplateConst.DEFAULT_ELEMENT_STROKE,
        strokeWidth: TemplateConst.FOLDING_STROKEWIDTH,
        hitStrokeWidth: TemplateConst.LINE_HITSTROKEWIDTH,
        draggable: true,
        shadowEnabled: false,
        strokeScaleEnabled: false,
        dash: [TemplateConst.FOLDING_DASH, TemplateConst.FOLDING_DASH],
        lineJoin: 'round',
        srcFoldId: folding.srcFoldId,
        dragBoundFunc: function(pos) {
          return {
            x: this.absolutePosition().x,
            y: this.absolutePosition().y
          }
        }
      }
      if (folding.srcFoldId === TemplateConst.FOLDING_ID_DUIZHE) { // 對摺
        var duizhiId = uuidv4()
        var duizhi = {
          ...config,
          points: [0, topHeightPx + height / 2, stageWidth, topHeightPx + height / 2],
          id: duizhiId
        }
        folding.ids.push(duizhiId)
        configArray.push(duizhi)
      } else if (folding.srcFoldId === TemplateConst.FOLDING_ID_FENGQINZHE2TIAOGU) { // 風琴摺2條骨
        var fengqinzhe2tiaogu_1_Id = uuidv4()
        var fengqinzhe2tiaogu_2_Id = uuidv4()
        var fengqinzhe2tiaogu_1 = {
          ...config,
          points: [leftWidthPx + width / 3, 0, leftWidthPx + width / 3, stageHeight],
          id: fengqinzhe2tiaogu_1_Id
        }
        var fengqinzhe2tiaogu_2 = {
          ...config,
          points: [leftWidthPx + width / 3 * 2, 0, leftWidthPx + width / 3 * 2, stageHeight],
          dash: [0, 0],
          id: fengqinzhe2tiaogu_2_Id
        }
        folding.ids.push(fengqinzhe2tiaogu_1_Id)
        folding.ids.push(fengqinzhe2tiaogu_2_Id)
        configArray.push(fengqinzhe2tiaogu_1)
        configArray.push(fengqinzhe2tiaogu_2)
      } else if (folding.srcFoldId === TemplateConst.FOLDING_ID_HEBAOZHE) { // 荷包摺
        var hebaozhe_1_Id = uuidv4()
        var hebaozhe_2_Id = uuidv4()
        var hebaozhe_1 = {
          ...config,
          points: [0, topHeightPx + height / 3, stageWidth, topHeightPx + height / 3],
          id: hebaozhe_1_Id
        }
        var hebaozhe_2 = {
          ...config,
          points: [0, topHeightPx + height / 3 * 2, stageWidth, topHeightPx + height / 3 * 2],
          id: hebaozhe_2_Id
        }
        folding.ids.push(hebaozhe_1_Id)
        folding.ids.push(hebaozhe_2_Id)
        configArray.push(hebaozhe_1)
        configArray.push(hebaozhe_2)
      }
      const NodeClass = Konva['Line']
      var sourceHistory = []
      configArray.forEach(config => {
        const shape = new NodeClass(config)
        this.getMaskLayerNode().add(shape)
        shape.moveToBottom()
        sourceHistory.push(initHistoryData(shape.id(), 'destroy', { 'className': shape.getClassName(), 'konva': shape.getAttrs(), 'text': folding.text }))
      })
      store.commit('template/addFolding', { ids: folding.ids, srcFoldId: folding.srcFoldId, text: folding.text })
      this.addUndo(sourceHistory, true)
    },
    // No后加工
    addNoText(data) {
      const m = 28 / 6
      const widthmm = data.bit * m
      const size = getPx(widthmm, store.state.template.dpi)
      const config = {
        id: uuidv4(),
        x: 0,
        y: 0,
        text: data.num,
        fontFamily: FontData[0].value,
        fontFamilyName: FontData[0].name,
        fill: data.numcolor,
        name: 'design Fit  NoText',
        draggable: true,
        scale: {
          x: 1,
          y: 0.53
        },
        srcNoTextItem: data
      }
      const shape = new Konva.Text(config)
      var fontSize = 12 / (shape.width() / size)
      shape.setFontSize(fontSize)
      this.moveToCenter(shape)
      this.getEditLayerNode().add(shape)
      this.tranformerSelected([shape])
      this.addUndo(initHistory(shape.id(), 'destroy', { 'className': shape.getClassName(), 'konva': shape.getAttrs() }), true)
      store.commit('template/addNoText', { id: config.id, data, width: size, mm: widthmm })
    },
    // No双击
    NoTextdbClick(e, isEdit = false) {
      NoEdit.edit({ ...e.target.getAttrs(), ...{ edit: true }}, (e) => {
        var data = { text: e.srcNoTextItem.num, fill: e.srcNoTextItem.numcolor }
        this.setAttrs(data)
        store.commit('template/editNoText', { id: e.id, data: e.srcNoTextItem })
      })
    },
    // 闸圆角(半径(单位px)，左上角，右上角，左下角，右下角),目前只支持同时闸4个角,如果要闸特殊角，需要注意处理正方面对称注意处理落單數據方面
    roundCorner(r, lt, rt, lb, rb, srcIsLock) {
      const oldData = { corner: false }
      const data = { corner: true, r, lt, rt, lb, rb, srcIsLock }
      store.commit('template/roundCorner', data)
      stageTool.roundCorner(this.$refs.stage.getStage(), data)
      this.addUndo(initHistory(TemplateConst.ROUND_CORNER_ID, 'roundCorner', oldData), true)
    },
    cancelRoundCorner() {
      const oldData = store.state.template.roundCorner
      const data = { corner: false }
      store.commit('template/roundCorner', data)
      stageTool.roundCorner(this.$refs.stage.getStage(), data)
      this.addUndo(initHistory(TemplateConst.ROUND_CORNER_ID, 'roundCorner', oldData), true)
      // const konvaLayer = this.getMaskLayerNode()
      // konvaLayer.destroyChildren()
      // const pageLayer = store.getters.maskLayer(store.state.design.pageId)
      // this.assembleLayer(pageLayer, konvaLayer)
    },

    addText(text) {
      const config = {
        id: uuidv4(),
        x: 0,
        y: 0,
        text: text,
        fontSize: store.state.design.fontSize,
        fontFamily: FontData[0].value,
        fontFamilyName: FontData[0].name,
        fill: '#000000',
        name: 'design FitScale FitScaleText text', // FitScaleText用于变形时只支持改变文字大小
        // name: 'design textPath FitScale',
        draggable: true,
        scale: {
          x: 1,
          y: 1
        },
        rotation: 0,
        letterSpacing: 0,
        lineHeight: 1,
        padding: 0,
        wrap: 'none'// 禁止tranform拉小是进行换行
      }
      const shape = new Konva.Text(config)
      // const shape = new Konva.TextPath(config)
      this.moveToCenter(shape)
      this.getEditLayerNode().add(shape)
      this.tranformerSelected([shape])
      this.addUndo(initHistory(shape.id(), 'destroy', { 'className': shape.getClassName(), 'konva': shape.getAttrs() }), true)
      store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(shape) })
    },
    // 转换方法一-日
    turnWeekday(day) {
      switch (day) {
        case '一':
          return 1
        case '二':
          return 2
        case '三':
          return 3
        case '四':
          return 4
        case '五':
          return 5
        case '六':
          return 6
        case '日':
          return 7
      }
    },
    addCalendar(data) {
      const yearandmonth = data.month.split('-')
      const params = {
        'year': yearandmonth[0],
        'month': yearandmonth[1],
        'area': 'HK'
      }
      // 获取请求
      const loading = this.$loading({
        lock: true,
        text: '日历生成中',
        spinner: 'el-icon-loading',
        background: 'rgba(0, 0, 0, 0.7)'
      })
      post('common', 'calendar/list', params).then(res => {
        loading.close()
        if (res.success) {
          if (res.data.length > 0) {
            const dateList = res.data
            const target = this.generateCalendar(data, dateList)
            const fontheight = target.fontSize() * target.textArr.length * target.lineHeight() + target.padding() * 2
            if (data.lunarOrSolarTerm !== 'gregorianCalendar') {
              this.generateCalendar(data, dateList, fontheight, true)
            }
          } else {
            bisErrorMsg('没有找到数据，请稍后重试')
          }
        }
      }, (error) => {
        bisErrorMsg(error)
        loading.close()
      })
    },

    generateCalendar(data, dateList, fontHeight, lunar = false) {
      // 根据data请求api获取数据
      var contentNode = this.getContentAreaLineNode()
      var blankGap = this.turnWeekday(dateList[0].weekday) // 前面空白位（从星期几开始）
      const dayNum = dateList.length// 一个多少天
      const col = 7// 一个星期的排列方式
      const row = Math.ceil((dayNum + blankGap) / col) // 行
      const width = (data.width ? data.width : contentNode.width()) / col
      const height = (data.height ? data.height : contentNode.height()) / row
      let shape = null
      for (var i = 0; i < dayNum; i++) {
        var c = blankGap + i
        const config = {
          id: uuidv4(),
          x: contentNode.x() + (c % col) * width, // 取余
          y: (contentNode.y() + parseInt(c / col) * height) + (lunar ? fontHeight : 0),
          text: lunar ? dateList[i].cDay : dateList[i].day + '',
          fontSize: lunar ? data.lunarFontSize : data.gregorianFontSize,
          fontFamily: lunar ? data.lunarFontFamily : data.gregorianFontFamily,
          fill: lunar ? data.lunarColor : data.gregorianColor,
          name: 'design FitScale FitScaleText text', // FitScaleText用于变形时只支持改变文字大小
          draggable: true,
          scale: {
            x: 1,
            y: 1
          },
          rotation: 0,
          letterSpacing: 0,
          lineHeight: 1,
          wrap: 'none'// 禁止tranform拉小是进行换行
        }
        // 周末
        if ((c % col === 0 || c % col === 6) && !lunar) {
          config.fill = data.weekendColor
        }
        if (data.lunarOrSolarTerm !== 'lunarCalendar' && dateList[i].term) {
          config.fill = data.weekendColor
        }
        if (data.lunarOrSolarTerm === 'SolarTerm' && lunar) {
          config.text = dateList[i].term ? dateList[i].term : ''
        } else if (data.lunarOrSolarTerm === 'All' && lunar) {
          config.text = dateList[i].term ? dateList[i].term : dateList[i].cDay
        }
        shape = new Konva.Text(config)
        this.getEditLayerNode().add(shape)
      }
      return shape
    },
    // 添加铁圈
    addWireo(url) {
      const item = {
        url
      }
      stageTool.getImage(uuidv4(), item.url).then(({ id, img }) => {
        item.width = img.width
        item.height = img.height
        item.orginUrl = url
        const config = {
          id,
          srcFillPatternItem: item,
          fillPatternX: 0,
          fillPatternY: 0,
          fillPatternScaleX: 1,
          fillPatternScaleY: 1,
          fillPatternImage: img,
          fillPatternRepeat: 'no-repeat',
          width: img.width, height: img.height, strokeWidth: 0,
          name: 'design nonTransform wireo',
          stroke: TemplateConst.DEFAULT_ELEMENT_STROKE,
          srcQrData: item, // 自定义值
          draggable: true,
          fillPriority: 'pattern',
          dashEnabled: false,
          shadowEnabled: false,
          strokeScaleEnabled: false,
          shadowForStrokeEnabled: false
        }
        const NodeClass = Konva['Rect']
        const shape = new NodeClass(config)
        this.moveToCenter(shape)// 移动到居中
        this.getMaskLayerNode().add(shape)
        shape.moveToBottom()
        this.tranformerSelected([shape])
        this.editAttrs()
      })
    },
    addCircleText(text) {
      var contentNode = this.getContentAreaLineNode()
      var r = (Math.min(contentNode.width(), contentNode.height() - TemplateConst.DEFAULT_STROKE_WIDTH)) / 2

      // var r = Math.max(contentNode.width() / 2, 50)
      const config = {
        id: uuidv4(),
        x: 0,
        y: 0,
        text: text,
        data: circle(r),
        srcRadius: r,
        fontSize: 30,
        fontFamily: FontData[0].value,
        fontFamilyName: FontData[0].name,
        fill: '#000000',
        name: 'design FitScale CircleText',
        draggable: true,
        scale: {
          x: 1,
          y: 1
        },
        rotation: 0,
        letterSpacing: 0,
        lineHeight: 1,
        wrap: 'none'// 禁止tranform拉小是进行换行

        // wrap: 'none', // 禁止tranform拉小是进行换行
        // kerningFunc() { // 字间距
        //   return 0.1
        // }
      }
      const shape = new Konva.TextPath(config)
      this.moveToCenter(shape)
      this.getEditLayerNode().add(shape)
      this.tranformerSelected([shape])
      this.addUndo(initHistory(shape.id(), 'destroy', { 'className': shape.getClassName(), 'konva': shape.getAttrs() }), true)
      store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(shape) })
    },
    addFreeImage(name, item) { // 直接添加自由图片类型
      var contentNode = this.getContentAreaLineNode()
      const w = Math.min(contentNode.width(), TemplateConst.MAX_LOAD_IMAGE)
      const h = Math.min(contentNode.height(), TemplateConst.MAX_LOAD_IMAGE)
      item.url = imgPorxy(item.orginUrl, w + ':' + h)// 加载合适大小的图片尺寸，减小网络消耗,bgFill是全画布的尺寸
      var _this = this
      Konva.Image.fromURL(fullUrl(item.url), function(darthNode) { // darthNode是image node
        const scale = getFitScale(contentNode.width(), contentNode.height(), darthNode.getAttrs().image.width, darthNode.getAttrs().image.height)
        darthNode.setAttrs({
          id: uuidv4(),
          x: 0,
          y: 0,
          scaleX: scale,
          scaleY: scale,
          width: darthNode.getAttrs().image.width,
          height: darthNode.getAttrs().image.height,
          name: 'design ' + name,
          draggable: true,
          srcImageItem: { id: item.id, orginUrl: item.orginUrl, name: item.name, url: item.url,
            orginWidth: item.orginWidth, orginHeight: item.orginHeight, // 原始图片大小
            width: darthNode.getAttrs().image.width, height: darthNode.getAttrs().image.height// 使用图片大小
          }
        })
        _this.moveToCenter(darthNode)
        _this.getEditLayerNode().add(darthNode)
        _this.tranformerSelected([darthNode])
        _this.addUndo(initHistory(darthNode.id(), 'destroy', { 'className': darthNode.getClassName(), 'konva': darthNode.getAttrs() }), true)
        store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(darthNode) })
      }, function(e) {
        console.log(e)
      })
    },
    addFrameImage(frameShape, srcFillPatternItem) { // 往相框添加图片
      if (frameShape.hasName('frame')) { // 属于相框
        const bgFill = this.getBgFillNode()
        const w = Math.min(bgFill.width(), TemplateConst.MAX_LOAD_IMAGE)
        const h = Math.min(bgFill.height(), TemplateConst.MAX_LOAD_IMAGE)
        srcFillPatternItem.url = imgPorxy(srcFillPatternItem.orginUrl, w + ':' + h)// 加载合适大小的图片尺寸，减小网络消耗,bgFill是全画布的尺寸
        // 使用图片作为填充的方式，填充在元素里面
        this.processNodeFillPatternItem(frameShape, srcFillPatternItem, () => { // load完图片回调
          // frameShape.fill(null)
          frameShape.fillPatternRotation(0)
          // 进行居中平铺
          const frameSize = stageTool.getSize(frameShape)
          const patternSale = getFillScale(frameSize.width, frameSize.height, srcFillPatternItem.width, srcFillPatternItem.height)
          frameShape.fillPatternScaleX(patternSale)
          frameShape.fillPatternScaleY(patternSale)
          const fillpos = stageTool.getFillPatternCenterPos(frameShape, srcFillPatternItem.width * patternSale, srcFillPatternItem.height * patternSale)
          frameShape.fillPatternX(fillpos.x)
          frameShape.fillPatternY(fillpos.y)
          frameShape.setAttr('srcFillPatternItem', srcFillPatternItem)

          store.commit('design/dirty', true) // 需更新狀態(因爲沒有實現undoredo)
          this.debounceSavePage(true, store.state.design.pageId)
        })
      }
    },
    addImage: debounce(function(item) {
      const tfnodes = this.getTransformerNode().nodes()
      var frameShape
      if (item.isUpload) {
        this.addRectFillimg('design nonTransform image Rect', item)
        return
      }
      if (tfnodes && tfnodes.length > 0) {
        if (tfnodes[0].hasName('frame')) {
          frameShape = tfnodes[0]
        }
      }
      if (frameShape) { // 有相框，丢到相框
        this.addFrameImage(frameShape, item)
      } else { // 无相框。直接作为图片丢到舞台
        this.addRectFrame('design nonTransform image Rect', item)
      }
    }, 500),
    addRectElement() { // 添加正方形
      var node = this.getContentAreaLineNode()
      var w = Math.min(node.width() - TemplateConst.DEFAULT_STROKE_WIDTH, node.height() - TemplateConst.DEFAULT_STROKE_WIDTH)
      this.addElement('Rect', {
        width: w, height: w, strokeWidth: TemplateConst.DEFAULT_STROKE_WIDTH,
        name: 'design element Rect',
        fill: TemplateConst.DEFAULT_ELEMENT_FILL, stroke: TemplateConst.DEFAULT_ELEMENT_STROKE
      })
    },
    addPreviewBox() {
      if (this.getKonvaNode(TemplateConst.PREVIEW_BOX)) {
        bisErrorMsg('已存在預覽框')
        return
      }
      var contentNode = this.getContentAreaLineNode()
      var config = {
        id: TemplateConst.PREVIEW_BOX,
        width: contentNode.width(),
        height: contentNode.height(),
        strokeWidth: 2,
        dash: [2, 2],
        name: 'design previewbox',
        stroke: '#000',
        x: 0,
        y: 0,
        draggable: true,
        shadowEnabled: false,
        strokeScaleEnabled: false,
        dashEnabled: true,
        opacity: 0.8,
        fillEnabled: false
      }
      const NodeClass = Konva['Rect']
      const shape = new NodeClass(config)
      this.moveToCenter(shape)
      this.getMaskLayerNode().add(shape)
      this.tranformerSelected([shape])
    },
    // 开窗
    addCutWindow() {
      const width = getPx(TemplateConst.CUT_WINDOW_WIDTH, store.state.template.dpi)
      const height = getPx(TemplateConst.CUT_WINDOW_HEIGHT, store.state.template.dpi)
      const radius = getPx(TemplateConst.CUT_WINDOW_RADIUS, store.state.template.dpi)
      var config = {
        id: uuidv4(),
        fillPatternRepeat: 'repeat',
        srcFillPatternItem: TemplateConst.TRANS_BG_FillPATTERN_ITEM(),
        width,
        height,
        cornerRadius: radius,
        // strokeWidth: 2,
        dash: [2, 2],
        name: 'design cutWindow',
        stroke: '#07E128',
        x: 0,
        y: 0,
        draggable: true,
        shadowEnabled: false,
        strokeScaleEnabled: false,
        dashEnabled: true,
        opacity: 0.8,
        // fillEnabled: false,
        hitFunc: function(context) { // 响应区域函数, 選中後，空白區域也能將其拖動
          context.beginPath()
          context.rect(0, 0, width, height)
          context.closePath()
          context.fillStrokeShape(this)
        }
      }
      const NodeClass = Konva['Rect']
      const shape = new NodeClass(config)
      this.processNodeFillPatternItem(shape, config.srcFillPatternItem)// 处理填充图片
      this.moveToCenter(shape)
      this.getMaskLayerNode().add(shape)
      this.tranformerSelected([shape])
    },
    // 不可编辑区域
    addNonEdit() {
      var contentNode = this.getContentAreaLineNode()
      var config = {
        id: uuidv4(),
        width: contentNode.width() / 2,
        height: contentNode.height() / 2,
        strokeWidth: 2,
        name: 'design element NonEdit',
        fill: '#696b6e',
        x: 0,
        y: 0,
        draggable: true,
        shadowEnabled: false,
        strokeScaleEnabled: false,
        dashEnabled: true,
        opacity: 0.8
      }
      const NodeClass = Konva['Rect']
      const shape = new NodeClass(config)
      this.moveToCenter(shape)
      this.getEditLayerNode().add(shape)
      this.tranformerSelected([shape])
      this.addUndo(initHistory(shape.id(), 'destroy', { 'className': shape.getClassName(), 'konva': shape.getAttrs() }), true)
      store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(shape) })
    },
    addRectFrame(name, imgItem, data) { // 添加方形相框与图片
      // item 包含  { id: , orginUrl: , name: , orginWidth: , orginHeight: }
      // 转换item 包含  { id: , orginUrl: , name: , orginWidth: , orginHeight:,url:,width:,height: }
      var contentNode = this.getContentAreaLineNode()

      if (imgItem) { // 有图片。需要加入相框
        const w = Math.min(contentNode.width(), TemplateConst.MAX_LOAD_IMAGE)
        const h = Math.min(contentNode.height(), TemplateConst.MAX_LOAD_IMAGE)
        imgItem.url = imgPorxy(imgItem.orginUrl, w + ':' + h)// 加载合适大小的图片尺寸，减小网络消耗,bgFill是全画布的尺寸
        var _this = this
        stageTool.getImage(uuidv4(), imgItem.url).then(function({ id, img }) {
          imgItem.width = img.width
          imgItem.height = img.height
          _this.addFrame('Rect', {
            srcFillPatternItem: imgItem,
            fillPatternX: 0,
            fillPatternY: 0,
            fillPatternScaleX: 1,
            fillPatternScaleY: 1,
            fillPatternImage: img,
            width: img.width, height: img.height, strokeWidth: 0,
            name: name, stroke: TemplateConst.DEFAULT_ELEMENT_STROKE,
            srcQrData: { ...data } // 自定义值
          })
        }).catch(function() {
          console.log('loaded image error')
        })
      } else {
        var w = Math.min(contentNode.width() - TemplateConst.DEFAULT_STROKE_WIDTH, contentNode.height() - TemplateConst.DEFAULT_STROKE_WIDTH)
        this.addFrame('Rect', {
          width: w, height: w, strokeWidth: TemplateConst.DEFAULT_STROKE_WIDTH,
          name: name, stroke: TemplateConst.DEFAULT_ELEMENT_STROKE, fill: TemplateConst.DEFAULT_ELEMENT_FILL
        })
      }
    },
    addRectFillimg(name, imgItem) { // 添加方形相框与图片铺满
      // item 包含  { id: , orginUrl: , name: , orginWidth: , orginHeight: }
      // 转换item 包含  { id: , orginUrl: , name: , orginWidth: , orginHeight:,url:,width:,height: }
      var contentNode = this.getContentAreaLineNode()

      if (imgItem) { // 有图片。需要加入相框
        const bgFill = this.getBgFillNode()
        const w = bgFill.width()
        const h = bgFill.height()
        imgItem.url = imgPorxy(imgItem.orginUrl, w + ':' + h)// 加载合适大小的图片尺寸，减小网络消耗,bgFill是全画布的尺寸
        var _this = this
        stageTool.getImage(uuidv4(), imgItem.url).then(function({ id, img }) {
          imgItem.width = img.width
          imgItem.height = img.height
          _this.addFrame('Rect', {
            srcFillPatternItem: imgItem,
            fillPatternX: 0,
            fillPatternY: 0,
            fillPatternScaleX: w / img.width,
            fillPatternScaleY: h / img.height,
            fillPatternImage: img,
            width: w, height: h, strokeWidth: 0,
            name: name, stroke: TemplateConst.DEFAULT_ELEMENT_STROKE
          })
        }).catch(function() {
          console.log('loaded image error')
        })
      } else {
        var w = Math.min(contentNode.width() - TemplateConst.DEFAULT_STROKE_WIDTH, contentNode.height() - TemplateConst.DEFAULT_STROKE_WIDTH)
        this.addFrame('Rect', {
          width: w, height: w, strokeWidth: TemplateConst.DEFAULT_STROKE_WIDTH,
          name: name, stroke: TemplateConst.DEFAULT_ELEMENT_STROKE, fill: TemplateConst.DEFAULT_ELEMENT_FILL
        })
      }
    },
    // 添加svg
    addSvg(data) {
      // 处理数据
      // parse content
      const parsed = parse(data.content)
      const svgdata = parsed.children[0].children
      let svgPath = '' // 路径
      let fill = data.fill
      let opacity = 1
      const viewBox = parsed.children[0].properties.viewBox.split(' ')
      const width = Number(viewBox[2])
      const height = Number(viewBox[3])
      svgdata.forEach((item) => {
        if (item.properties.d) {
          svgPath += item.properties.d // 路径拼接
          fill = item.properties.fill ? item.properties.fill : data.fill
          opacity = item.properties.opacity ? item.properties.option : opacity
        }
      })
      this.addElement('Path', {
        fill,
        opacity,
        width,
        height,
        data: svgPath,
        fillPatternX: 0,
        fillPatternY: 0,
        fillPatternScaleX: 1,
        fillPatternScaleY: 1,
        scaleX: 0.5,
        scaleY: 0.5,
        strokeWidth: 0,
        name: 'design FitScale Path',
        hitFunc: function(context) { // 响应区域函数, 選中後，空白區域也能將其拖動
          context.beginPath()
          context.rect(0, 0, width, height)
          context.closePath()
          context.fillStrokeShape(this)
        }
      }, true)
    },
    addGrid(data) {
      data.col = Number(data.col)
      data.row = Number(data.row)
      data.strokeWidth = Number(data.strokeWidth)
      var node = this.getContentAreaLineNode()
      var w = Math.max(node.width() - 10, 50)
      var h = Math.max(node.height() - 10, 50)
      const pathdata = grid(0, 0, w, h, data.col, data.row)
      this.addElement('Path', {
        x: 0,
        y: 0,
        width: w,
        height: h,
        data: pathdata,
        strokeWidth: data.strokeWidth,
        fill: data.fill,
        stroke: data.stroke,
        name: 'design Grid',
        srcGridData: data
      }, true)
    },
    addCircleFrame() { // 添加圆形相框
      var node = this.getContentAreaLineNode()
      var w = Math.min(node.width() - TemplateConst.DEFAULT_STROKE_WIDTH, node.height() - TemplateConst.DEFAULT_STROKE_WIDTH)
      this.addFrame('Circle', {
        radius: w / 2,
        width: w, height: w, strokeWidth: TemplateConst.DEFAULT_STROKE_WIDTH,
        name: 'design frame nonTransform image FitScale Circle', stroke: TemplateConst.DEFAULT_ELEMENT_STROKE, fill: TemplateConst.DEFAULT_ELEMENT_FILL
      })
    },
    addTriangleFrame() { // 添加三角相框
      var node = this.getContentAreaLineNode()
      var w = Math.min(node.width() - TemplateConst.DEFAULT_STROKE_WIDTH, node.height() - TemplateConst.DEFAULT_STROKE_WIDTH)
      this.addFrame('RegularPolygon', {
        sides: 3,
        radius: w / 2,
        name: 'design frame nonTransform image FitScale Triangle',
        strokeWidth: TemplateConst.DEFAULT_STROKE_WIDTH,
        stroke: TemplateConst.DEFAULT_ELEMENT_STROKE, fill: TemplateConst.DEFAULT_ELEMENT_FILL
      })
    },
    addCircleElement() { // 添加圆形
      var node = this.getContentAreaLineNode()
      var w = Math.min(node.width() - TemplateConst.DEFAULT_STROKE_WIDTH, node.height() - TemplateConst.DEFAULT_STROKE_WIDTH)
      this.addElement('Circle', {
        radius: w / 2,
        width: w, height: w, strokeWidth: TemplateConst.DEFAULT_STROKE_WIDTH,
        name: 'design element FitScale Circle',
        fill: TemplateConst.DEFAULT_ELEMENT_FILL, stroke: TemplateConst.DEFAULT_ELEMENT_STROKE
      })
    },
    addTriangleElement() { // 添加三角形
      var node = this.getContentAreaLineNode()
      var w = Math.min(node.width() - TemplateConst.DEFAULT_STROKE_WIDTH, node.height() - TemplateConst.DEFAULT_STROKE_WIDTH)
      this.addElement('RegularPolygon', {
        sides: 3,
        radius: w / 2,
        name: 'design element FitScale Triangle',
        strokeWidth: TemplateConst.DEFAULT_STROKE_WIDTH,
        fill: TemplateConst.DEFAULT_ELEMENT_FILL, stroke: TemplateConst.DEFAULT_ELEMENT_STROKE
      })
    },
    addLineElement() { // 添加线条
      var node = this.getContentAreaLineNode()
      var w = Math.min(node.width() - TemplateConst.DEFAULT_STROKE_WIDTH, node.height() - TemplateConst.DEFAULT_STROKE_WIDTH)
      this.addElement('Line', {
        points: [0, 0, w, 0],
        name: 'design element Line',
        stroke: TemplateConst.DEFAULT_ELEMENT_STROKE, strokeWidth: TemplateConst.DEFAULT_STROKE_WIDTH, hitStrokeWidth: TemplateConst.LINE_HITSTROKEWIDTH
      })
    },
    addFrame: debounce(function(className, config) {
      config.id = uuidv4()
      if (className === 'Circle' || className === 'RegularPolygon') { // 圆和等边三角形，使用的是中心点作为原始参考坐标
        config.x = config.radius
        config.y = config.radius
      } else {
        config.x = 0
        config.y = 0
      }
      config.fillPriority = 'pattern'
      config.draggable = true
      config.dashEnabled = false
      config.shadowEnabled = false
      config.strokeScaleEnabled = true // 邊框的縮放,当父节点进行缩放的时候，边框会被缩放
      config.shadowForStrokeEnabled = false
      config.fillPatternRepeat = 'no-repeat'
      const NodeClass = window.Konva[className]
      const shape = new NodeClass(config)
      this.moveToCenter(shape)// 移动到居中
      this.getEditLayerNode().add(shape)
      this.tranformerSelected([shape])
      this.editAttrs()
      if (shape.hasName('frame')) { // 激活左側圖片導航欄
        this.$emit('changeTab', 'Upload')
      }
      this.addUndo(initHistory(shape.id(), 'destroy', { 'className': shape.getClassName(), 'konva': shape.getAttrs() }), true)
      store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(shape) })
    }, 500),
    addElement: debounce(function(className, config, cache) {
      config.id = uuidv4()
      config.x = 0
      config.y = 0
      config.draggable = true
      config.dashEnabled = false
      config.shadowEnabled = false
      config.strokeScaleEnabled = true // 邊框的縮放
      config.shadowForStrokeEnabled = false
      const NodeClass = window.Konva[className]
      const shape = new NodeClass(config)
      this.moveToCenter(shape)
      this.getEditLayerNode().add(shape)
      this.tranformerSelected([shape])
      if (cache) {
        shape.cache()
      }
      // this.addUndo(initHistory(shape.id(), 'destroy', { 'className': shape.getClassName(), 'konva': shape.getAttrs() }), true)
      store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(shape) })
      return shape
    }, 500),
    setAttrs(config) {
      const tr = this.getTransformerNode()
      var sourceHistory = []
      tr.nodes().forEach(shape => {
        if (shape.hasName('design')) {
          if (shape.hasName('Path')) {
            shape.clearCache()
          }
          var historyData = setHistoryCompareData(shape.id(), 'attrs', config, shape.getAttrs())
          if (historyData != null) {
            sourceHistory.push(historyData)
            shape.setAttrs(config)
            if (shape.hasName('Path')) {
              shape.cache()
            }
          }
        }
      })
      tr.forceUpdate()
      this.addUndo(sourceHistory, true)
    },
    // 公共填充背景方法
    bgFillCommon(config) { // fill,srcFillPatternItem(id,orginUrl)
      var srcFillPatternItem = config.srcFillPatternItem
      if (srcFillPatternItem && srcFillPatternItem.id === TemplateConst.TRANS_BG_IMAGE_URL) { // 修正fillPatternRepeat
        config.fillPatternRepeat = 'repeat'// 透明背景图需要重复
      } else {
        if (!config.fillPatternRepeat) {
          config.fillPatternRepeat = 'no-repeat'// 默认使用no-repeat，只有背景图使用transparent透明式，才使用repeat
        }
      }
      var bgFill = this.getBgFillNode()
      store.commit('design/bgFill', config.fill)// 保存当前背景数据，用于回填
      store.commit('design/bgFillPatternItem', config.srcFillPatternItem)// 保存当前背景数据，用于回填
      var historyData = setHistoryData(bgFill.id(), 'bg', config, bgFill.getAttrs())
      this.processNodeFillPatternItem(bgFill, srcFillPatternItem, (srcItem) => {
        if (srcItem && srcItem.id && srcItem.id !== TemplateConst.TRANS_BG_IMAGE_URL) {
          const scale = Math.max(bgFill.width() / srcItem.width, bgFill.height() / srcItem.height)// 背景采用低尺寸模式填充，后台再使用高质量图片填充
          bgFill.fillPatternScaleX(scale)
          bgFill.fillPatternScaleY(scale)
          bgFill.fillPatternX(-(srcItem.width * scale - bgFill.width()) / 2)// 居中放置
          bgFill.fillPatternY(-(srcItem.height * scale - bgFill.height()) / 2)// 居中放置
        }
      })
      bgFill.setAttrs(config)
      return historyData
    },
    clearBgFill() { // 使用透明背景
      this.bgFill({ srcFillPatternItem: TemplateConst.TRANS_BG_FillPATTERN_ITEM(), fill: '' })
    },
    bgFill(config) { // fill,srcFillPatternItem(id,orginUrl原始图连接,url要加载的优化后连接)
      var bgFill = this.getBgFillNode()
      if (config.srcFillPatternItem) {
        const w = Math.min(bgFill.width(), TemplateConst.MAX_LOAD_IMAGE)
        const h = Math.min(bgFill.height(), TemplateConst.MAX_LOAD_IMAGE)
        config.srcFillPatternItem.url = imgPorxy(config.srcFillPatternItem.orginUrl, w + ':' + h)// 加载背景大小的图片尺寸，减小网络消耗,fill模式是需要全铺模式
      }
      var historyData = this.bgFillCommon(config)
      this.addUndo([historyData], true)
    },
    // 公共旋轉方法
    addRotateCommon(tr, rotation) {
      if (tr.nodes().length > 0 && !tr.nodes()[0].hasName('Fit')) {
        var oldAttrs = {
          x: tr.getX(),
          y: tr.getY(),
          width: tr.getWidth(),
          height: tr.getHeight(),
          rotation: tr.rotation()
        }
        var newAttrs = {
          x: tr.getX(),
          y: tr.getY(),
          width: tr.getWidth(),
          height: tr.getHeight(),
          rotation: tr.rotation()
        }
        const aroundpoint = getRotateAroundPoint(oldAttrs, rotation, getCenter(oldAttrs.x, oldAttrs.y, oldAttrs.width, oldAttrs.height, oldAttrs.rotation))
        newAttrs.x = aroundpoint.x
        newAttrs.y = aroundpoint.y
        newAttrs.rotation = Konva.getAngle(aroundpoint.rotation)
        tr._fitNodesInto(newAttrs)
      }
    },
    // 獲取旋轉的undo history數據
    addRotateHistoryData(tr, rotation) {
      if (tr.nodes().length > 0 && !tr.nodes()[0].hasName('Fit')) {
        var ids = tr.nodes().map(node => node.id())
        var data = { 'rotation': -rotation }
        return initHistoryData(ids, 'rotation', data)
      }
      return null
    },
    // toolbar旋转tranformer
    addRotate(rotation) {
      const tr = this.getTransformerNode()
      this.addRotateCommon(tr, rotation)
      var sourceHistory = []
      var historyData = this.addRotateHistoryData(tr, rotation)
      if (historyData != null) {
        sourceHistory.push(historyData)
        this.addUndo(sourceHistory, true)
      }
    },
    // 公共縮放方法
    addScaleCommon(tr, scale) {
      if (tr.nodes().length > 0) {
        var oldAttrs = {
          x: tr.getX(),
          y: tr.getY(),
          width: tr.getWidth(),
          height: tr.getHeight(),
          rotation: tr.rotation()
        }
        var newAttrs = getScaleAroundCenter(oldAttrs, 1 + scale)
        newAttrs.rotation = Konva.getAngle(newAttrs.rotation)// 需要使用angle
        tr._fitNodesInto(newAttrs)
      }
    },
    // 獲取縮放的undo history數據
    addScaleHistoryData(tr, scale) {
      if (tr.nodes().length > 0) {
        var ids = tr.nodes().map(node => node.id())
        var data = { 'scale': -scale }
        return initHistoryData(ids, 'scale', data)
      }
      return null
    },
    // toolbar缩放tranformer
    addScale(scale) {
      const tr = this.getTransformerNode()
      this.addScaleCommon(tr, scale)
      var sourceHistory = []
      var historyData = this.addScaleHistoryData(tr, scale)
      if (historyData != null) {
        sourceHistory.push(historyData)
      }
      this.addUndo(sourceHistory, true)
    },
    // toolbar自适应画布
    fitScaleStage(stage) {
      // const stage = this.$refs.stage.getStage()
      if (!stage) { // 沒有傳，則獲取當前舞臺
        stage = this.$refs.stage.getStage()
      }
      var konvacontainer = document.getElementById('konvacontainer')
      // 使用原始尺寸计算缩放比例。原始尺寸srcWidth一只保持不变
      var scale = Math.min(konvacontainer.clientWidth / stage.getAttr('srcWidth'), (konvacontainer.clientHeight - 200) / stage.getAttr('srcHeight'))
      if (scale > 1) {
        scale = 1
      }
      this.addScaleStage(scale - Math.max(stage.scaleX(), stage.scaleY()), stage)
    },
    // toolbar缩放画布
    addScaleStage(scale, stage) { // 必须保持stage.scaleX，stage.scaleY 同比例缩放
      // const stage = this.$refs.stage.getStage()
      if (!stage) { // 沒有傳，則獲取當前舞臺
        stage = this.$refs.stage.getStage()
      }
      const newScaleX = stage.scaleX() + scale
      const newscaleY = stage.scaleY() + scale
      const newWidth = stage.getAttr('srcWidth') * newScaleX
      const newHeight = stage.getAttr('srcHeight') * newscaleY
      if (newWidth < 50 || newHeight < 50 || newScaleX <= 0 || newscaleY <= 0) {
        return
      }
      // const center = {
      //   x: stage.x() + stage.width() * stage.scaleX() / 2,
      //   y: stage.y() + stage.height() * stage.scaleY() / 2
      // }

      // const newCenter = {
      //   x: stage.x() + stage.width() * stage.scaleX() / 2,
      //   y: stage.y() + stage.height() * stage.scaleY() / 2
      // }
      // const distance = {
      //   x: center.x - newCenter.x,
      //   y: center.y - newCenter.y
      // }
      stage.scaleX(newScaleX)
      stage.scaleY(newscaleY)
      // stage.setAttrs({
      //   x: stage.x() + distance.x,
      //   y: stage.y() + distance.y
      // })
      stage.setAttrs({
        width: newWidth,
        height: newHeight
      })
      this.getTransformerNode().forceUpdate()
      stage.batchDraw()
    },
    handleKeyBoard(keyCode, ctrlKey, altKey, shiftKey) { // 处理键盘事件
      if (keyCode === 46 || keyCode === 110) {
        // delete
        this.delete()
      } else if (keyCode === 37 || keyCode === 38 || keyCode === 39 || keyCode === 40) { // left、up、right、down
        this.move(keyCode)
      } else if (ctrlKey && keyCode === 67) {
        this.keyCopy()
      } else if (ctrlKey && keyCode === 86) {
        this.keyPaste()
      } else if (ctrlKey && altKey) {
        switch (keyCode) {
          case 221:
            this.textLayer('top')
            break
          case 219:
            this.textLayer('bottom')
            break
        }
      } else if (ctrlKey && keyCode === 221) {
        this.textLayer('up')
      } else if (ctrlKey && keyCode === 219) {
        this.textLayer('down')
      } else if (shiftKey && ctrlKey && keyCode === 90) { // 下一步
        this.redo()
      } else if (ctrlKey && keyCode === 90) { // 上一步
        this.undo()
      }
    },
    // 通过键盘复制
    keyCopy() {
      store.commit('template/cleanCopy')
      var tr = this.getTransformerNode()
      if (tr.nodes().length === 0) {
        return
      }
      var nodes = tr.nodes().sort((a, b) => {
        return a.zIndex() > b.zIndex() ? 1 : -1
      })
      nodes.forEach(shape => {
        if (shape.hasName('design') && !shape.hasName('Fit')) {
          var newShape = shape.clone()
          newShape.setAttr('id', uuidv4())
          store.commit('template/copy', newShape)
        }
      })
    },
    // 粘贴
    keyPaste: debounce(function() {
      var tr = this.getTransformerNode()

      const sourceHistory = []
      var newNodes = []
      store.state.template.copy.forEach((item) => {
        const newShape = item.clone()
        newShape.setAttr('id', uuidv4())
        this.getEditLayerNode().add(newShape)

        store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(newShape) })
        sourceHistory.push(initHistoryData(newShape.id(), 'destroy', { 'className': newShape.getClassName(), 'konva': newShape.getAttrs() }))
        newNodes.push(newShape)
      })
      if (store.state.template.copy.length > 0) {
        this.clearTransformer()
        tr.nodes(newNodes)
        this.editAttrs()
        this.addUndo(sourceHistory, true)
      }
    }, 500),
    // 下一步
    redo() {
      if (store.getters.redoHistory && store.getters.redoHistory.length > 0) {
        store.commit('history/popRedoHistory', store.state.design.pageId)
        var sourceHistory = this.undoRedoCommon(store.getters.popRedo, 'redo')
        store.commit('history/clearPopRedo')
        this.addUndo(sourceHistory, false)
      }
    },
    // 上一步
    undo() {
      if (store.getters.undoHistory && store.getters.undoHistory.length > 0) {
        store.commit('history/popUndoHistory', store.state.design.pageId)
        var sourceHistory = this.undoRedoCommon(store.getters.popUndo, 'undo')
        store.commit('history/clearPopUndo')
        this.addRedo(sourceHistory)
      }
    },
    // 獲取上一步下一步數據
    undoRedoCommon(history, action) {
      var sourceHistory = []
      var shape, tr, groupData, historyData
      if (history.length === 0) {
        return sourceHistory
      }
      const config = {
        id: uuidv4(),
        x: 0,
        y: 0,
        name: 'design group',
        draggable: true
      }
      const group = new Konva.Group(config)
      history.forEach(element => {
        if (element.type === 'attrs') {
          shape = this.getKonvaNode(element.id)
          sourceHistory.push(setHistoryData(shape.id(), element.type, element.data, shape.getAttrs()))
          shape.setAttrs(element.data)
          if (shape.hasName('design') && shape.hasName('element')) {
            this.$emit('actattrs', { activate: 'element', attrs: shape.getAttrs() })
          }
        } else if (element.type === 'bg') {
          sourceHistory.push(this.bgFillCommon(element.data))
        } else if (element.type === 'scale' || element.type === 'rotation' || element.type === 'flip') {
          tr = this.getTransformerNode()
          tr.nodes([]) // 清空
          element.id.forEach(id => {
            var shape = this.getKonvaNode(id)
            var nodes = tr.nodes().concat([shape])
            tr.nodes(nodes)
          })
          if (element.type === 'scale') { // 縮放
            this.addScaleCommon(tr, element.data.scale)
            historyData = this.addScaleHistoryData(tr, element.data.scale)
          } else if (element.type === 'rotation') { // 旋轉
            this.addRotateCommon(tr, element.data.rotation)
            historyData = this.addRotateHistoryData(tr, element.data.rotation)
          } else if (element.type === 'flip') { // 翻轉
            this.flipXYCommon(tr, element.data.scaleX, element.data.scaleY)
            historyData = this.flipXYHistoryData(tr, element.data.scaleX, element.data.scaleY)
          }
          if (historyData != null) {
            sourceHistory.push(historyData)
          }
        } else if (element.type === 'destroy') {
          var node = this.getKonvaNode(element.id)
          if (typeof (node) === 'undefined') {
            if (element.data.className === 'Group') { // 判斷是否群組
              shape = element.data.konva
              this.getEditLayerNode().add(shape)
              store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(shape) })
              // element.data = { 'className': shape.getClassName(), 'konva': shape, 'shapeId': shape.id(), 'shapeName': shape.getClassName() }
            } else {
              if (element.data.konva.name === 'design Fit Punch') {
                this.addKonvaShape(element.data, this.getMaskLayerNode())
                var px = getPx(element.data.konva.mm, store.state.template.dpi)
                store.commit('template/addPunch', { id: element.id, px, mm: element.data.konva.mm, dpi: store.state.template.dpi })
              } else if (element.data.konva.name === TemplateConst.CRIMPLINE_NAME) {
                this.addKonvaShape(element.data, this.getMaskLayerNode())
                store.commit('template/addCrimpLine', { id: element.id, direction: element.data.konva.direction })
              } else if (element.data.konva.name === TemplateConst.NEEDLELINE_NAME) {
                this.addKonvaShape(element.data, this.getMaskLayerNode())
                store.commit('template/addNeedleLine', { id: element.id, direction: element.data.konva.direction })
              } else if (element.data.konva.name === TemplateConst.FOLDING_NAME) {
                this.addKonvaShape(element.data, this.getMaskLayerNode())
                store.commit('template/addFolding', { ids: [element.id], srcFoldId: element.data.konva.srcFoldId, text: element.data.text })
              } else {
                this.addKonvaShape(element.data, this.getEditLayerNode())
                store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(this.getKonvaNode(element.id)) })
              }
            }
          } else {
            var cloneNode = node.clone()
            if (element.data.className === 'Group') { // 判斷是否群組
              element.data.konva = cloneNode
            } else {
              element.data.konva = cloneNode.attrs
            }
            node.destroy()
            if (element.data.konva.name === 'design Fit Punch') {
              store.commit('template/deletePunch', { id: element.id })
            } else if (element.data.konva.name === TemplateConst.CRIMPLINE_NAME) {
              store.commit('template/deleteCrimpLine', { id: element.id })
            } else if (element.data.konva.name === TemplateConst.NEEDLELINE_NAME) {
              store.commit('template/deleteNeedleLine', { id: element.id })
            } else if (element.data.konva.name === TemplateConst.FOLDING_NAME) {
              store.commit('template/deleteFolding', { srcFoldId: element.data.konva.srcFoldId })
            } else {
              store.commit('template/deleteEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(cloneNode) })
            }
          }
          sourceHistory.push(initHistoryData(element.id, element.type, element.data))
          this.clearTransformer()
        } else if (element.type === 'group') { // 判斷是否群組undoredo操作
          node = this.getKonvaNode(element.id)
          if (element.data.shapeName === 'Group') {
            if (element.data.className === 'Group') { // 處理群組內群組undo
              if (element.data.konva.attrs) {
                tr = this.getKonvaNode(element.data.konva.attrs.id)
              } else {
                tr = new Konva.Text(element.data.konva)
              }
              if (element.data.unClass === 'Text') {
                element.id = element.data.konva.attrs.id
              }
              groupData = element.data.groupData
              tr.setAttrs({ draggable: true, x: (tr.attrs.x) + (groupData.x), y: (tr.attrs.y) + (groupData.y) })
              this.getEditLayerNode().add(tr)
              this.clearTransformer()
              store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(tr) })
              element.data.shapeName = 'Text'
            } else { // 處理群組除群組undoredo
              this.addKonvaShape(element.data, this.getEditLayerNode())
              if (element.data.unClass === 'Text') {
                element.id = element.data.konva.id
              }
              store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(this.getKonvaNode(element.id)) })
              if (element.data.unClass === 'Text') {
                element.id = element.data.konva.id
              }
              element.data.shapeName = 'Text'
            }
            if (this.getKonvaNode(element.data.shapeId)) {
              node = this.getKonvaNode(element.data.shapeId)
              element.data.groupData = node.attrs
              store.commit('template/deleteEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(node) })
              node.destroy()
            }
          } else if (element.data.shapeName === 'Text') { // 處理群組redo
            if (element.data.groupData) {
              group.setAttrs(element.data.groupData)
            }
            tr = this.getKonvaNode(element.id)
            var trData = tr.clone()
            var groupId = trData.attrs.id
            trData.setAttrs({ id: uuidv4(), draggable: false })
            if (element.data.unClass === 'Text') {
              element.data.className = tr.getClassName()
              element.data.konva = tr.attrs
              element.id = element.data.shapeId
            }
            tr.destroy()
            store.commit('template/deleteEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(tr) })
            trData.setAttrs({ id: groupId, draggable: false, x: (trData.attrs.x) - (group.attrs.x), y: (trData.attrs.y) - (group.attrs.y) })
            this.getEditLayerNode().add(trData)
            if (element.data.unClass === 'Text') {
              if (tr.getClassName() === 'Group') {
                element.data.konva = trData
              }
            }
            group.setAttrs({ id: element.data.shapeId })
            group.add(this.getKonvaNode(trData.attrs.id))
            element.data.shapeName = 'Group'
          }
          sourceHistory.push(initHistoryData(element.id, element.type, element.data))
          this.clearTransformer()
        } else if (element.type === 'lock' || element.type === 'visible') {
          shape = this.getKonvaNode(element.id)
          if (element.type === 'lock') {
            sourceHistory.push(initHistoryData(element.id, element.type, { draggable: !element.data.draggable, listening: !element.data.listening }))
          } else {
            sourceHistory.push(initHistoryData(element.id, element.type, { visible: !element.data.visible }))
          }
          shape.setAttrs(element.data)
          store.commit('template/updateEditNode', { pageId: store.state.design.pageId, dataArray: [{ 'id': element.id, 'value': element.data }] })
          this.clearTransformer()
        } else if (element.type === 'clip') { // 遮罩undo、redo操作
          this.clearTransformer()
          var maskOrUnmaskHistory = []
          if (element.data.className === 'Group') {
            shape = this.getKonvaNode(element.id)
            this.tranformerSelected([shape])
            maskOrUnmaskHistory = this.unMask()
          } else {
            var shapes = []
            element.id.forEach(_id => {
              shapes.push(this.getKonvaNode(_id))
            })
            this.tranformerSelected(shapes)
            maskOrUnmaskHistory = this.mask(element.data.groupId)
          }
          if (maskOrUnmaskHistory.length > 0) {
            sourceHistory.push(initHistoryData(maskOrUnmaskHistory[0].id, maskOrUnmaskHistory[0].type, { className: maskOrUnmaskHistory[0].data.className, groupId: maskOrUnmaskHistory[0].data.groupId }))
          }
        } else if (element.type === 'shapePosition') { // 處理各元素位置更改的undo、redo操作
          shape = this.getEditLayerNode().findOne('#' + element.id)
          this.updateDragCommon(shape, element.data.zIndex, true)
          historyData = this.updateDragHistoryData(shape, element.id, element.data.zIndex)
          if (historyData != null) {
            sourceHistory.push(historyData)
          }
        } else if (element.type === 'roundCorner') { // 處理閘圓角undo、redo操作
          var oldCornerData
          if (element.data.corner) {
            oldCornerData = { corner: false }
          } else {
            oldCornerData = element.data
          }
          store.commit('template/roundCorner', element.data)
          stageTool.roundCorner(this.$refs.stage.getStage(), element.data)
          sourceHistory.push(initHistoryData(element.id, element.type, oldCornerData))
        } else if (element.type === 'move') { // 處理鍵盤上下左右微調undo、redo操作
          this.clearTransformer()
          var moveShapes = []
          element.id.forEach(_id => {
            moveShapes.push(this.getKonvaNode(_id))
          })
          this.tranformerSelected(moveShapes)
          historyData = this.moveCommon(element.data.keyCode)
          if (historyData && typeof (historyData) !== 'undefined') {
            sourceHistory.push(historyData)
          }
        } else if (element.type === 'crimpOrNeedleLine') { // 處理壓線、針線位置修改undo、redo操作
          shape = this.getKonvaNode(element.id)
          if (element.data.direction === TemplateConst.LINE_DIRECTION_H) { // 橫
            historyData = { 'direction': element.data.direction, 'konva': { y: shape.getY() }}
          } else if (element.data.direction === TemplateConst.LINE_DIRECTION_V) { // 豎
            historyData = { 'direction': element.data.direction, 'konva': { x: shape.getX() }}
          }
          sourceHistory.push(initHistoryData(element.id, element.type, historyData))
          shape.setAttrs(element.data.konva)
        }
      })
      if (group.children.length > 0) {
        this.getEditLayerNode().add(group)
        // this.clearTransformer()
        store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(group) })
      }
      if (sourceHistory) {
        this.editAttrs()
      }
      return sourceHistory
    },
    // 无实现功能，暂时注释掉
    // 顯示隱藏虛綫網格
    // gridsSet() {
    //   var myCanvas = document.getElementsByClassName('konvajs-content')[0].lastElementChild
    //   var ctx = myCanvas.getContext('2d')
    //   var gridSize = 50
    //   var canvasHeight = this.designTemplate.bleedAreaHeight + 115
    //   var canvasWidth = this.designTemplate.bleedAreaWidth + 35
    //   var xLineTotal = Math.floor(canvasHeight / gridSize)
    //   var yLineTotal = Math.floor(canvasWidth / gridSize)
    //   if (this.showCum) {
    //     const canvasW = myCanvas.width
    //     myCanvas.width = canvasW
    //     this.showCum = false
    //   } else {
    //     ctx.setLineDash([3])
    //     for (var i = 2.6; i <= xLineTotal; i++) {
    //       ctx.moveTo(this.bleedPos.x, i * gridSize - 0.5)
    //       ctx.lineTo(canvasWidth, i * gridSize - 0.5)
    //     }
    //     for (var j = 0; j <= yLineTotal; j++) {
    //       ctx.moveTo(j * gridSize - 0.5, this.bleedPos.y)
    //       ctx.lineTo(j * gridSize - 0.5, canvasHeight)
    //     }
    //     ctx.stroke()
    //     ctx.strokeStyle = 'black'
    //     ctx.beginPath()
    //     this.showCum = true
    //   }
    // },
    print() {
    },
    // konva保存到template狀態公共方法
    saveCommon(isClearFlag) {
      if (!store.state.design.pageId || store.state.design.pageId === '') { // 无任何打开的页面
        return
      }

      if (!store.state.design.dirty) { // 无编辑过，无需保存
        return
      }
      if (!isClearFlag) { // 是否需要clearTr，沒有传此參數或者传false則clear；传true不clear
        this.clearTransformer()
      }
      const stageNode = this.$refs.stage.getStage()
      var stage = {}
      var konva = {} // 需要重新定義，不能直接賦值
      Object.keys(stageNode.getAttrs()).forEach(key => {
        if (key !== 'container' && key !== 'dragBoundFunc' && key !== 'clipFunc') { // 这些字段不需要存储
          konva[key] = stageNode.getAttrs()[key]
        }
      })
      stage.className = stageNode.nodeType
      stage.konva = konva
      // delete stage.konva.container
      // stage.konva.container = undefined
      stage.layers = []
      stage.layers.push(toTemplateNode(this.getBackgroundLayerNode()))
      stage.layers.push(toTemplateNode(this.getEditLayerNode()))
      // stage.layers.push(toTemplateNode(this.getMaskLayerNode()))
      const maskLayer = toTemplateNode(this.getMaskLayerNode())
      store.commit('template/setStage', { pageId: store.state.design.pageId, stage, maskLayer })
    },
    // konva保存到template
    save() {
      this.saveCommon()
    },
    debounceSavePage: debounce(function(isClearFlag, pageId) {
      this.saveCommon(isClearFlag)
      this.$emit('savePage', pageId)
    }, 800),
    ReplaceId(node) {
      if (node && node.konva && node.konva.id) {
        node.konva.id = uuidv4()
      }
      if (node && node.children && node.children.length > 0) {
        node.children.forEach(child => {
          this.ReplaceId(child)
        })
      }
    },
    merge(page) { // 文字模板
      const editLayer = this.getEditLayerNode()
      if (page.stage.layers && page.stage.layers.length > 0) {
        var sourceHistory = []
        page.stage.layers.forEach(layer => {
          if (layer.konva.id === TemplateConst.EDITLAYER_ID && layer.children && layer.children.length > 0) {
            layer.children.forEach(node => {
              this.ReplaceId(node)// 防止多次merge产生template内的shape 相同的id
              this.addKonvaShape(node, editLayer)
              sourceHistory.push(initHistoryData(node.konva.id, 'destroy', { 'className': node.className, 'konva': node.konva }))
              store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: node })
            })
            return
          }
        })
        this.addUndo(sourceHistory, true)
      }
      editLayer.batchDraw()
    },
    getFonts(node, fonts) {
      if (node.className === 'Text') {
        if (node.konva && node.konva.fontFamily) {
          if (fonts.indexOf(node.konva.fontFamily) === -1) {
            fonts.push(node.konva.fontFamily)
          }
        }
      }

      if (node.children && node.children.length > 0) {
        node.children.forEach(child => {
          this.getFonts(child, fonts)
        })
      }
    },
    updatePage(page) {
      this.load(page)
    },
    load(page) {
      store.commit('design/currentPage', page.id)
      const stage = this.$refs.stage.getStage()
      stage.destroyChildren()
      page.stage.konva.width = page.stage.konva.srcWidth// 使用原始尺寸打开
      page.stage.konva.height = page.stage.konva.srcHeight
      // delete page.stage.konva.container
      stage.setAttrs(page.stage.konva)
      if (page.stage.layers && page.stage.layers.length > 0) {
        var fonts = getFonts(page)// 打开之前，先加载所有字体
        if (fonts && fonts.length > 0) {
          var loads = []
          fonts.forEach(font => {
            var loaFont = new Fontfaceobserver(font)
            loads.push(loaFont.load(null, 30000))
          })

          Promise.all(loads).then(function() {
            console.log('loaded font')
            stage.batchDraw()
          }).catch(function() {
            console.log('loaded font error')
            stage.batchDraw()
          })
        }
        page.stage.layers.forEach(layer => {
          const shape = new Konva.Layer(layer.konva)
          this.assembleLayer(layer, shape)
          if (TemplateConst.BACKGROUNDLAYER_ID !== shape.getId()) { // 背景Layer不需要
            this.lineGuide(shape)
            this.dragMove(shape)
            this.mouseStyle(shape)
          }
          stage.add(shape)
        })
      }
      const maskType = store.getters.maskType(page.id)
      if (maskType.mask) { // 共用mask
        var layer = store.state.template.maskLayer
        if (layer) {
          if (maskType.reverseH) {
            store.commit('design/dirty', true)// masklayer发生了改变
            layer = reverseHorizontal(layer)
          }
          const shape = new Konva.Layer(layer.konva)
          this.assembleLayer(layer, shape)
          this.lineGuide(shape)
          this.dragMove(shape)
          this.mouseStyle(shape)
          stage.add(shape)
        }
      }
      // 添加toplayer选择框和tranformer层
      const topLayer = new Konva.Layer({ id: 'toptopLayer', name: 'toptopLayer' })
      topLayer.add(new Konva.Rect({ id: TemplateConst.SELECT_RECT_ID, name: TemplateConst.SELECT_RECT_ID, visible: false, fill: 'rgba(0,0,255,0.5)' }))// 多选选择框
      // 提示(如壓線位置尺寸提示)
      var tooltipLayer = new Konva.Layer({ id: TemplateConst.TOOLTIPLAYER_ID, name: TemplateConst.TOOLTIPLAYER_ID })
      tooltipLayer.add(new Konva.Text({ id: TemplateConst.TOOLTIP_ID, name: TemplateConst.TOOLTIP_NAME, text: '', fontFamily: 'Calibri', fontSize: 16, padding: 5, textFill: 'white', fill: 'black', alpha: 0.75, visible: false }))
      // 选中变形框

      const transformer = new Konva.Transformer({ id: TemplateConst.TRANSFORMER_ID, rotationSnaps: [0, 45, 90, 135, 180, 225, 270, 315], name: TemplateConst.TRANSFORMER_ID, flipEnabled: false, useSingleNodeRotation: true })
      transformer.on('transformstart', this.handleTransformstart)
      transformer.on('transformend', this.handleTransformend)
      transformer.on('dragstart', this.handleTrDragstart)
      transformer.on('dragend', this.handleTrDragend)
      transformer.on('transform', this.tranferOnTransform)
      topLayer.add(transformer)
      stage.add(topLayer)
      stage.add(tooltipLayer)
      stage.batchDraw()
      this.fitScaleStage(stage)
      this.getKonvaNode(TemplateConst.TOOLTIP_ID).setAttr('fontSize', 16 / stage.getScaleX())
    },
    // 从现有template，打开另外一个page到konva
    open(page) {
      this.save()
      this.load(page)
    },
    layerSelectShape(id) {
      if (id === TemplateConst.BACKGROUNDLAYER_ID) {
        return
      }
      var node = this.getEditLayerNode().findOne('#' + id)
      if (node) {
        if (!node.hasName('design')) { // 点击的不是可编辑设计元素
          return
        }
        if (typeof (node.listening()) !== 'undefined' && !node.listening()) { // 鎖定了
          return
        }
        if (typeof (node.visible()) !== 'undefined' && !node.visible()) { // 隱藏了
          return
        }
        this.getTransformerNode().nodes([node])
        this.editAttrs()
      }
    },
    layerSelectMaskShape(id) {
      if (id === TemplateConst.BACKGROUNDLAYER_ID) {
        return
      }
      var node = this.getMaskLayerNode().findOne('#' + id)
      if (node) {
        if (!node.hasName('design')) { // 点击的不是可编辑设计元素
          return
        }
        if (typeof (node.listening()) !== 'undefined' && !node.listening()) { // 鎖定了
          return
        }
        if (typeof (node.visible()) !== 'undefined' && !node.visible()) { // 隱藏了
          return
        }
        this.getTransformerNode().nodes([node])
        // this.editAttrs()
      }
    },
    // 水平平均分隔位置
    horizontalAverage() {
      const tr = this.getTransformerNode()
      const num = tr.nodes().length
      if (num < 2) {
        return
      }
      var minX = 100000000
      var maxX = -100000000
      tr.nodes().forEach(node => {
        var position = node.absolutePosition()
        minX = Math.min(minX, position.x)
        maxX = Math.max(maxX, position.x)
      })

      const gap = (maxX - minX) / (num - 1)
      // 按照x值排序
      tr.nodes().sort((a, b) => {
        return a.absolutePosition().x - b.absolutePosition().x
      })
      tr.nodes().forEach(node => {
        var position = node.absolutePosition()
        node.absolutePosition({
          x: minX,
          y: position.y
        })
        minX = minX + gap
      })
    },
    // 垂直平均分隔位置
    verticalAverage() {
      const tr = this.getTransformerNode()
      const num = tr.nodes().length
      if (num < 2) {
        return
      }
      var minY = 100000000
      var maxY = -100000000
      tr.nodes().forEach(node => {
        var position = node.absolutePosition()
        minY = Math.min(minY, position.y)
        maxY = Math.max(maxY, position.y)
      })

      const gap = (maxY - minY) / (num - 1)
      tr.nodes().sort((a, b) => {
        return a.absolutePosition().y - b.absolutePosition().y
      })
      tr.nodes().forEach(node => {
        var position = node.absolutePosition()
        node.absolutePosition({
          x: position.x,
          y: minY
        })
        minY = minY + gap
      })
    },
    // 底部对齐
    alignBottom() {
      const tr = this.getTransformerNode()
      const num = tr.nodes().length
      if (num < 2) {
        return
      }
      var bottomY = -100000000// 最底部坐标（）
      tr.nodes().forEach(node => {
        var pos = node.getClientRect()// clientrect需要是绝对坐标
        bottomY = Math.max(bottomY, pos.y + pos.height)
      })

      tr.nodes().forEach(node => {
        console.log(node.getHeight())
        var originPos = node.getClientRect()
        // console.log(originPos)
        // console.log(node.textHeight, '文字')
        console.log(originPos.height, 'originPos')
        console.log(bottomY, 'bottomY')
        const distance = bottomY - (originPos.y + originPos.height)// 向下移动多少距离
        var position = node.absolutePosition()
        node.absolutePosition({
          x: position.x,
          y: position.y + distance// 因为ClientRect是绝对坐标，所以使用设置绝对坐标的移动方式
        })
      })
    },
    // 字體對齊处理
    alignBtn(e) {
      switch (e) {
        case 'horizontal' :// 水平平均分隔位置
          this.horizontalAverage()
          return
        case 'vertical' :// 垂直平均分隔位置
          this.verticalAverage()
          return
        case 'down':
          this.alignBottom()
          return
      }

      const trList = this.getTransformerNode()
      var printData = this.getKonvaNode(TemplateConst.PRINT_AREA_LINE_ID).attrs // 對齊時按哪裏對齊 要選擇安全區對齊就更改成CONTENTAREALINE_ID
      var sourceHistory = []
      trList.nodes().forEach(tr => {
        var cpoint = getCenter(tr.getX(), tr.getY(), tr.getWidth() * tr.getScaleX(), tr.getHeight() * tr.getScaleX(), tr.rotation())
        var scale = this.$refs.stage.getStage().attrs.scaleX
        const rect = tr.getClientRect()
        var xw = tr.attrs.x - (cpoint.x - (rect.width / scale / 2))
        var yw = tr.attrs.y - (cpoint.y - (rect.height / scale / 2))
        if (tr.getAttr('name') === 'design element Circle' || tr.getAttr('name') === 'design element Triangle') {
          xw = rect.width / scale / 2
          yw = rect.height / scale / 2
        }
        var x = 0
        var y = 0
        var config = {}
        switch (e) {
          case 'left' :// 左對齊
            x = xw + printData.x
            config = { x: x }
            break
          case 'center':
            x = (printData.width / 2 + printData.x) - (rect.width / scale / 2) + xw
            config = { x: x }
            break
          case 'right':
            x = printData.x + printData.width - (rect.width / scale) + xw
            config = { x: x }
            break
          case 'top':
            y = yw + printData.y
            config = { y: y }
            break
          case 'centerVer':
            y = (printData.height / 2 + printData.y) - (rect.height / scale / 2) + yw
            config = { y: y }
            break
          case 'down':
            y = printData.y + printData.height - (rect.height / scale) + yw
            config = { y: y }
            break
        }
        var historyData = setHistoryCompareData(tr.id(), 'attrs', config, tr.getAttrs())
        if (historyData != null) {
          sourceHistory.push(historyData)
          tr.setAttrs(config)
        }
      })
      this.addUndo(sourceHistory, true)
    },
    // 层上下移动
    textLayer(e) {
      const trList = this.getTransformerNode().nodes()
      if (trList.length > 1) {
        return
      }
      trList.forEach(tr => {
        switch (e) {
          case 'up':
            tr.moveUp()
            break
          case 'down':
            tr.moveDown()
            break
          case 'top':
            tr.moveToTop()
            break
          case 'bottom':
            tr.moveToBottom()
            break
        }
        // 修改左側圖層元素順序
        store.commit('template/textLayerEditNode', { pageId: store.state.design.pageId, dataArray: { 'node': toTemplateNode(tr), 'value': e }})
      })
    },
    // 字体透明度
    textSlider(e) {
      var val = 1 - (e / 100)
      const trList = this.getTransformerNode().nodes()
      trList.forEach(shape => {
        if (shape.hasName('design') && !shape.hasName('Fit')) {
          shape.opacity(val)
        }
      })
    },
    // 複製
    copy() {
      var tr = this.getTransformerNode()
      if (tr.nodes().length === 0) {
        return
      }
      // 目前只支持在editlayer层
      // 按照原始层级顺序复制
      var nodes = tr.nodes().sort((a, b) => {
        return a.zIndex() > b.zIndex() ? 1 : -1
      })
      var sourceHistory = []
      var newNodes = []
      nodes.forEach(shape => {
        if (shape.hasName('design') && !shape.hasName('Fit')) {
          var newShape = shape.clone()
          newShape.setAttr('id', uuidv4())
          // 目前只支持复制添加到editlayer
          this.getEditLayerNode().add(newShape)
          store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(newShape) })
          newNodes.push(newShape)
          // 添加undo history
          sourceHistory.push(initHistoryData(newShape.id(), 'destroy', { 'className': newShape.getClassName(), 'konva': newShape.getAttrs() }))
        }
      })
      if (newNodes.length > 0) {
        this.clearTransformer()
        tr.nodes(newNodes)
        this.editAttrs()
        this.addUndo(sourceHistory, true)
      }
    },

    delete(node = []) {
      var _this = this
      var tr = this.getTransformerNode() || node
      let nodes = tr.nodes()
      if (node.length > 0) {
        nodes = node
      }
      if (nodes.length > 0) {
        var sourceHistory = []
        nodes.forEach(shape => {
          if (shape.getClassName() === 'Group') {
            var group = shape.clone()
            sourceHistory.push(initHistoryData(group.id(), 'destroy', { 'className': group.getClassName(), 'konva': group }))
            store.commit('template/deleteEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(shape) })
            shape.destroy()
          } else {
            if (shape.getAttr('name') === TemplateConst.FOLDING_NAME) { // 摺工, 如荷包摺是兩條線，兩條線都需要刪掉
              var folding = store.state.template.foldings.find(item => item.srcFoldId === shape.getAttr('srcFoldId'))
              var foldingShape = ''
              folding.ids.forEach(id => {
                foldingShape = _this.getKonvaNode(id)
                sourceHistory.push(initHistoryData(foldingShape.id(), 'destroy', { 'className': foldingShape.getClassName(), 'konva': foldingShape.getAttrs(), 'text': folding.text }))
                foldingShape.destroy()
              })
              store.commit('template/deleteFolding', { srcFoldId: shape.getAttr('srcFoldId') })
            } else if (shape.getAttr('name') === TemplateConst.TOOLTIP_NAME) { // tooltip提示
              shape.hide() // 隱藏即可
            } else {
              sourceHistory.push(initHistoryData(shape.id(), 'destroy', { 'className': shape.getClassName(), 'konva': shape.getAttrs() }))
              if (shape.getAttr('name') === 'design Fit Punch') {
                store.commit('template/deletePunch', { id: shape.getAttr('id') })
              } else if (shape.getAttr('name') === TemplateConst.CRIMPLINE_NAME) {
                store.commit('template/deleteCrimpLine', { id: shape.getAttr('id') })
              } else if (shape.getAttr('name') === TemplateConst.NEEDLELINE_NAME) {
                store.commit('template/deleteNeedleLine', { id: shape.getAttr('id') })
              } else if (shape.hasName('ProcessingLeft') || shape.hasName('processing')) {
                store.commit('template/deleteProcessing')
              } else if (shape.hasName('NoText')) {
                store.commit('template/deleteNoText', { id: shape.getAttr('id') })
              } else {
                store.commit('template/deleteEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(shape) })
              }
              shape.destroy()
            }
          }
        })
        this.addUndo(sourceHistory, true)
        this.clearTransformer()
      }
    },
    // 添加Group
    groupBtn() {
      // var sourceHistory = []
      var trList = this.getTransformerNode()
      if (trList.nodes().length < 2) {
        if (trList.nodes()[0] && trList.nodes()[0].getClassName() === 'Group') {
          this.cancelGroup()
        }
        return
      }
      // 组合成group
      // var stageAttrs = this.$refs.stage.getStage().attrs
      const config = {
        id: uuidv4(),
        // x: (trList.getX()) / stageAttrs.scaleX,
        // y: (trList.getY()) / stageAttrs.scaleY,
        // width: trList.getWidth() / stageAttrs.scaleX,
        // height: trList.getHeight() / stageAttrs.scaleY,
        name: 'design FitScale group', // 目前群组功能不支持变形
        draggable: true
      }
      const group = new Konva.Group(config)
      this.getEditLayerNode().add(group)

      // 层级排序
      trList.nodes().sort((a, b) => {
        return a.getAbsoluteZIndex() - b.getAbsoluteZIndex()
      })

      trList.nodes().forEach(shape => {
        // var trData = tr.clone()
        // trData.setAttrs({ id: uuidv4(), draggable: false })
        // tr.destroy()
        // const newLoc = newLocation(group.x(), group.y(), shape.x(), shape.y())// 因为node是使用父节点的相对坐标，所以进新坐标转换
        // shape.setAttrs({ draggable: false, x: newLoc.x, y: newLoc.y })
        const oldParentTransform = shape.getParent().getAbsoluteTransform().copy()
        const newParentTransform = group.getAbsoluteTransform().copy()
        const oldLocalTransform = shape.getTransform().copy()
        const newLocalTransform = new Konva.Transform()
        newLocalTransform.multiply(newParentTransform.invert()).multiply(oldParentTransform).multiply(oldLocalTransform)
        const attrs = newLocalTransform.decompose()
        shape.setAttrs(attrs)
        shape.setAttrs({ draggable: false })

        group.add(shape)
        // trData.setAttrs({ id: tr.id(), draggable: false, x: newLoc.x, y: newLoc.y })
        // if (trData.getClassName() === 'Group') { // 判斷是否存在群組
        //   this.getEditLayerNode().add(trData)
        //   sourceHistory.push(initHistoryData(tr.id(), 'group', { 'className': tr.getClassName(), 'konva': tr, 'shapeId': config.id, 'shapeName': shape.getClassName(), 'groupData': shape.attrs }))
        // } else {
        //   this.getEditLayerNode().add(trData)
        //   sourceHistory.push(initHistoryData(tr.id(), 'group', { 'className': tr.getClassName(), 'konva': tr.getAttrs(), 'shapeId': config.id, 'shapeName': shape.getClassName() }))
        // }
        // group.add(this.getKonvaNode(trData.attrs.id))
        // store.commit('template/deleteEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(tr) })
      })

      // this.addUndo(sourceHistory, true)
      this.tranformerSelected([group])
      // store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(shape) })
    },
    // 取消群组
    cancelGroup() {
      // node parent AbsoluteTransform *  node Transform= node  AbsoluteTransform

      // old parent AbsoluteTransform *  node old Transform =  new parent AbsoluteTransform *  node  new Transform
      // node  new Transform=new parent AbsoluteTransform.invert() * old parent AbsoluteTransform *  node old Transform
      var groupNode = this.getTransformerNode().nodes()[0]

      const childrens = groupNode.getChildren().slice()
      childrens.forEach(node => {
        const oldParentTransform = groupNode.getAbsoluteTransform().copy()
        const newParentTransform = groupNode.getParent().getAbsoluteTransform().copy()
        const oldLocalTransform = node.getTransform().copy()
        const newLocalTransform = new Konva.Transform()
        newLocalTransform.multiply(newParentTransform.invert()).multiply(oldParentTransform).multiply(oldLocalTransform)
        const attrs = newLocalTransform.decompose()
        node.setAttrs(attrs)
        if (node.hasName('design')) {
          node.setAttrs({ draggable: true })
        }
        groupNode.getParent().add(node)
      })
      // const newParentTransform = groupNode.getParent().getAbsoluteTransform().copy()
      // var trList = this.getTransformerNode().nodes()[0]
      // var sourceHistory = []
      // var nodes = trList.clone()
      // var groupId = trList.attrs.id
      // nodes.setAttrs({ id: uuidv4() })
      // nodes.children.forEach(node => {
      //   var tr = node.clone()
      //   tr.setAttrs({ draggable: true, x: (node.attrs.x) + (nodes.attrs.x), y: (node.attrs.y) + (nodes.attrs.y) })
      //   this.getEditLayerNode().add(tr)
      //   store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(node) })
      //   if (this.getKonvaNode(groupId)) {
      //     store.commit('template/deleteEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(trList) })
      //     sourceHistory.push(initHistoryData(tr.id(), 'group', { 'className': tr.getClassName(), 'konva': node.attrs, 'shapeId': groupId, 'shapeName': 'Text', 'groupData': nodes.attrs, 'unClass': 'Text' }))
      //     nodes.setAttrs({ id: groupId })
      //     trList.destroy()
      //   } else {
      //     sourceHistory.push(initHistoryData(tr.id(), 'group', { 'className': tr.getClassName(), 'konva': node.attrs, 'shapeId': groupId, 'shapeName': 'Text', 'unClass': 'Text' }))
      //   }
      // })
      // this.addUndo(sourceHistory, true)
      this.clearTransformer()
    },
    // 公共翻轉方法
    flipXYCommon(tr, scaleX, scaleY) {
      if (tr.nodes().length > 0) {
        var oldAttrs = {
          x: tr.getX(),
          y: tr.getY(),
          width: tr.getWidth(),
          height: tr.getHeight(),
          rotation: tr.rotation()
        }
        var newAttrs = getFlipXY(oldAttrs, scaleX, scaleY)
        newAttrs.rotation = Konva.getAngle(newAttrs.rotation)// 需要使用angle
        tr._fitNodesInto(newAttrs)
      }
    },
    // 獲取翻轉的undo history數據
    flipXYHistoryData(tr, scaleX, scaleY) {
      if (tr.nodes().length > 0) {
        var ids = tr.nodes().map(node => node.id())
        var data = { 'scaleX': scaleX, 'scaleY': scaleY }
        return initHistoryData(ids, 'flip', data)
      }
      return null
    },
    // 翻轉
    flipXY(axis) {
      const tr = this.getTransformerNode()
      if (tr.nodes().length > 0) {
        var sourceHistory = []
        var historyData = null
        if (axis === 'x') { // 水平方向
          this.flipXYCommon(tr, -tr.getScaleX(), tr.getScaleY())
          historyData = this.flipXYHistoryData(tr, -tr.getScaleX(), tr.getScaleY())
          if (historyData != null) {
            sourceHistory.push(historyData)
          }
        } else if (axis === 'y') { // 垂直方向
          this.flipXYCommon(tr, tr.getScaleX(), -tr.getScaleY())
          historyData = this.flipXYHistoryData(tr, tr.getScaleX(), -tr.getScaleY())
          if (historyData != null) {
            sourceHistory.push(historyData)
          }
        }
        this.addUndo(sourceHistory, true)
      }
    },
    // 鎖定
    changeLock({ pageId, konvaId, draggable, listening }) {
      if (typeof (draggable) === 'undefined') {
        draggable = true
      }
      if (typeof (listening) === 'undefined') {
        listening = true
      }
      var shape = this.getKonvaNode(konvaId)
      var data = {
        draggable: !draggable,
        listening: !listening
      }
      shape.setAttrs(data)
      store.commit('template/updateEditNode', { pageId: pageId, dataArray: [{ 'id': konvaId, 'value': data }] })
      this.addUndo(initHistory(konvaId, 'lock', { draggable: !data.draggable, listening: !data.listening }), true)
      this.clearTransformer()
    },
    // 顯示與隱藏
    changeVisible({ pageId, konvaId, visible }) {
      if (typeof (visible) === 'undefined') {
        visible = true
      }
      var shape = this.getKonvaNode(konvaId)
      var data = {
        visible: !visible
      }
      shape.setAttrs(data)
      store.commit('template/updateEditNode', { pageId: pageId, dataArray: [{ 'id': konvaId, 'value': data }] })
      this.addUndo(initHistory(konvaId, 'visible', { visible: !data.visible }), true)
      this.clearTransformer()
    },
    maskBtn(e) {
      var sourceHistory = []
      if (e === 'unmask') {
        sourceHistory = this.unMask()
      } else if (e === 'mask') {
        sourceHistory = this.mask(uuidv4())
      }
      if (sourceHistory.length > 0) {
        this.addUndo(sourceHistory, true)
      }
    },
    // 添加遮罩
    mask(groupId) {
      var sourceHistory = []
      const trans = this.getTransformerNode()
      const trList = trans.nodes()
      const designElement = trList.find(node => node.hasName('design') && node.hasName('element'))// 遮罩物件
      if (designElement) {
        var flag = false // 是否符合
        const config = {
          id: groupId,
          x: 0,
          y: 0,
          name: 'design clip',
          draggable: true
        }
        var group = new Konva.Group(config)
        trList.forEach(node => {
          if (node.hasName('design') && !node.hasName('element') && !node.hasName('clip') && node.getClassName() !== TemplateConst.DESIGN_TEXT) {
            const newLoc = newLocation(group.x(), group.y(), node.x(), node.y())
            node.x(newLoc.x)
            node.y(newLoc.y)
            node.draggable(false)
            group.add(node)
            store.commit('template/deleteEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(node) })
            flag = true
          }
        })
        if (!flag) {
          return false
        }
        const newLoc = newLocation(group.x(), group.y(), designElement.x(), designElement.y())
        designElement.x(newLoc.x)
        designElement.y(newLoc.y)
        designElement.stroke(null)
        designElement.strokeWidth(null)
        designElement.draggable(false)
        designElement.globalCompositeOperation('destination-in') // 在新图形以及已有画布重叠的地方，已有内容都保留。所有其他内容成为透明的
        group.add(designElement)
        group.cache({ pixelRatio: 1 }) // 避免影響整個畫布的其他元素
        store.commit('template/deleteEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(designElement) })

        this.getEditLayerNode().add(group)
        this.clearTransformer()
        this.tranformerSelected([group])
        sourceHistory.push(initHistoryData(group.getId(), 'clip', { className: group.getClassName(), groupId: group.getId() }))
        store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(group) })
      }
      return sourceHistory
    },
    // 取消遮罩
    unMask() {
      var sourceHistory = []
      const trans = this.getTransformerNode()
      const trList = trans.nodes()
      if (trList.length > 0) {
        trList.forEach(node => {
          if (node.getClassName() === 'Group' && node.getAttr('name') === 'design clip') { // 組 -> 各個元素
            var cloneShape = []
            var ids = []
            var groupId = node.getId()
            node.getChildren().forEach(childNode => {
              var clone = childNode.clone() // 克隆
              if (clone.hasName('design') && clone.hasName('element')) { // 遮罩物件
                clone.setAttrs({ // 還原
                  stroke: TemplateConst.DEFAULT_ELEMENT_STROKE,
                  strokeWidth: TemplateConst.DEFAULT_STROKE_WIDTH,
                  draggable: true,
                  globalCompositeOperation: ''
                })
              } else { // 還原
                clone.setAttr('draggable', true)
              }
              ids.push(clone.getId())
              cloneShape.push(clone)
              this.getEditLayerNode().add(clone)
              store.commit('template/addEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(clone) })
            })
            node.destroy() // group銷燬
            store.commit('template/deleteEditNode', { pageId: store.state.design.pageId, node: toTemplateNode(node) })
            sourceHistory.push(initHistoryData(ids, 'clip', { className: '', groupId: groupId })) // 這裏有多個id，className可不需要
            this.tranformerSelected(cloneShape)
          }
        })
      }
      return sourceHistory
    },
    // 修改文本/圖片/元素/背景/mask層的srcKey、srcIsLock
    updateTextSrcKey(formModel) {
      var shape = this.getKonvaNode(formModel.konvaId)
      var srcKey = shape.getAttr('srcKey')
      // srcKey
      if (srcKey) {
        srcKey['text'] = formModel.textSrcKey
        shape.setAttr('srcKey', srcKey)
      } else {
        shape.setAttr('srcKey', { 'text': formModel.textSrcKey })
      }
      // 是否鎖定
      shape.setAttr('srcIsLock', formModel.srcIsLock)
      store.commit('template/updateTextSrcKey', formModel)
      bisSuccessMsg('成功')
    },
    // 模板加載，替換相同文本/圖片/元素/背景srcKey
    replaceTextSrcKey(oldPage, newPage, oldCommonMaskLayer, newCommonMaskLayer) {
      var replaceArray = []
      if (oldPage.stage.layers && oldPage.stage.layers.length > 0) {
        oldPage.stage.layers.forEach(layer => {
          if ((layer.konva.id === TemplateConst.EDITLAYER_ID || layer.konva.id === TemplateConst.BACKGROUNDLAYER_ID || layer.konva.id === TemplateConst.MASKLAYER_ID) && layer.children && layer.children.length > 0) {
            layer.children.forEach(node => {
              if (node.konva.srcKey && node.konva.srcKey['text']) {
                var key = node.konva.srcKey['text']

                var data = {}
                data['konvaId'] = node.konva.id
                data['konvaName'] = node.konva.name
                if (data.konvaName.indexOf('text') !== -1) { // 文本
                  data[key] = node.konva.text
                } else if (data.konvaName.indexOf('ill') !== -1 || data.konvaName.indexOf('image') !== -1 || data.konvaName.indexOf('frame') !== -1 || data.konvaName.indexOf('bgFill') !== -1) { // 圖片
                  if (node.konva.srcFillPatternItem) {
                    data[key] = { url: node.konva.srcFillPatternItem.url, orginUrl: node.konva.srcFillPatternItem.orginUrl }
                  }
                } else if (data.konvaName.indexOf('element') !== -1) { // 素材
                  data[key] = { className: node.className, konva: node.konva }
                } else { // mask層
                  data[key] = { konva: node.konva }
                }
                replaceArray.push(data)
              }
            })
          }
        })
      }
      if (newPage.stage.layers && newPage.stage.layers.length > 0 && replaceArray.length > 0) {
        var isUpdated = false
        var oldShape = ''
        newPage.stage.layers.forEach(layer => {
          if ((layer.konva.id === TemplateConst.EDITLAYER_ID || layer.konva.id === TemplateConst.BACKGROUNDLAYER_ID || layer.konva.id === TemplateConst.MASKLAYER_ID) && layer.children && layer.children.length > 0) {
            layer.children.forEach(node => {
              if (node.konva.srcKey && node.konva.srcKey['text']) {
                var key = node.konva.srcKey['text']

                replaceArray.some(item => {
                  if (key in item) { // 是否存在需要替換的相同key
                    if (item.konvaName.indexOf('text') !== -1) { // 文本
                      node.konva.text = item[key]
                    } else if (item.konvaName.indexOf('ill') !== -1 || item.konvaName.indexOf('image') !== -1 || item.konvaName.indexOf('frame') !== -1 || item.konvaName.indexOf('bgFill') !== -1) { // 圖片
                      if (node.konva.srcFillPatternItem) {
                        node.konva.srcFillPatternItem.url = item[key].url
                        node.konva.srcFillPatternItem.orginUrl = item[key].orginUrl
                      }
                    } else if (item.konvaName.indexOf('element') !== -1) { // 素材
                      node.className = item[key].className
                      Object.keys(item[key].konva).forEach(k => {
                        if (typeof (node.konva[k]) === 'undefined') { // 添加沒有的屬性，如舊三角形替換新圓
                          node.konva[k] = item[key].konva[k]
                        }
                      })
                    } else { // mask層
                      Object.keys(item[key].konva).forEach(k => {
                        if (k === 'x' || k === 'y' || k === 'width' || k === 'height') { // 替換部分屬性
                          node.konva[k] = item[key].konva[k]
                        }
                      })
                    }
                    isUpdated = true
                    oldShape = this.getKonvaNode(item.konvaId) // 刪掉舊的
                    if (oldShape && oldShape !== '') {
                      oldShape.destroy()
                    }
                    return true // 結束some循環
                  }
                })
              }
            })
            if (isUpdated) {
              layer.children.splice(layer.children.length) // 解決數據更新，頁面沒有渲染的問題
            }
          }
        })
      }
      // 公共mask層
      if (isCommonMaskLayer(store.state.template.templateType)) {
        var commonMaskReplaceArray = []
        if (oldCommonMaskLayer && oldCommonMaskLayer.children && oldCommonMaskLayer.children.length > 0) {
          oldCommonMaskLayer.children.forEach(node => {
            if (node.konva.srcKey && node.konva.srcKey['text']) {
              var key = node.konva.srcKey['text']

              var data = {}
              data['konvaId'] = node.konva.id
              data['konvaName'] = node.konva.name
              // mask層
              data[key] = { konva: node.konva }
              commonMaskReplaceArray.push(data)
            }
          })
        }
        if (newCommonMaskLayer && newCommonMaskLayer.children && newCommonMaskLayer.children.length > 0 && commonMaskReplaceArray.length > 0) {
          var isUpdatedCommonMask = false
          var oldShapeCommonMask = ''
          newCommonMaskLayer.children.forEach(node => {
            if (node.konva.srcKey && node.konva.srcKey['text']) {
              var key = node.konva.srcKey['text']

              commonMaskReplaceArray.some(item => {
                if (key in item) { // 是否存在需要替換的相同key
                // mask層
                  Object.keys(item[key].konva).forEach(k => {
                    if (k === 'x' || k === 'y' || k === 'width' || k === 'height') { // 替換部分屬性
                      node.konva[k] = item[key].konva[k]
                    }
                  })

                  isUpdatedCommonMask = true
                  oldShapeCommonMask = this.getKonvaNode(item.konvaId) // 刪掉舊的
                  if (oldShapeCommonMask && oldShapeCommonMask !== '') {
                    oldShapeCommonMask.destroy()
                  }
                  return true // 結束some循環
                }
              })
            }
          })
          if (isUpdatedCommonMask) {
            newCommonMaskLayer.children.splice(newCommonMaskLayer.children.length) // 解決數據更新，頁面沒有渲染的問題
          }
        }
      }
    },
    // 圖層拖動修改公共方法
    updateDragCommon(node, zIndex, isStoreUpdated) {
      if (node) {
        this.getTransformerNode().nodes([])// 取消transformer
        this.getTransformerNode().nodes([node])
        this.editAttrs()
        var direction = 'down' // 向下
        if (zIndex < 0) {
          direction = 'up' // 向上
        }
        const trList = this.getTransformerNode().nodes()
        for (var i = 0; i < Math.abs(zIndex); i++) {
          if (direction === 'down') {
            trList[0].moveDown()
          } else {
            trList[0].moveUp()
          }
          if (isStoreUpdated) {
            // 修改左側圖層元素順序
            store.commit('template/textLayerEditNode', { pageId: store.state.design.pageId, dataArray: { 'node': toTemplateNode(trList[0]), 'value': direction }})
          }
        }
      }
    },
    // 圖層拖動修改undo、redo的Data
    updateDragHistoryData(node, konvaId, zIndex) {
      if (node) {
        return initHistoryData(konvaId, 'shapePosition', { 'zIndex': -zIndex })
      }
      return null
    },
    // 圖層拖動修改
    updateDrag({ konvaId, zIndex }) {
      var sourceHistory = []
      var node = this.getEditLayerNode().findOne('#' + konvaId)
      this.updateDragCommon(node, zIndex, false)
      var historyData = this.updateDragHistoryData(node, konvaId, zIndex)
      if (historyData != null) {
        sourceHistory.push(historyData)
      }
      this.addUndo(sourceHistory, true)
    },
    // 鍵盤上下左右移動
    move(keyCode) {
      var historyData = this.moveCommon(keyCode)
      if (historyData && typeof (historyData) !== 'undefined') {
        var sourceHistory = []
        sourceHistory.push(historyData)
        this.addUndo(sourceHistory, true)
      }
    },
    moveCommon(keyCode) {
      const tr = this.getTransformerNode()
      if (tr.nodes().length > 0) {
        const stage = this.$refs.stage.getStage()
        const scale = stage.getScaleX()
        var direction = '' // 壓線方向
        var topHeightPx = ''
        var leftWidthPx = ''
        var isFoldingMove = false // 是否摺工
        var isLine = false // 是否design Line
        var isMinLine = false // 是否達到最小的Line
        tr.nodes().forEach(node => {
          if (TemplateConst.CRIMPLINE_NAME === node.getName() || TemplateConst.NEEDLELINE_NAME === node.getName()) { // 壓線、針線
            direction = node.getAttr('direction')
            if (TemplateConst.LINE_DIRECTION_H === direction) {
              topHeightPx = store.state.template.maskPadding[0] + store.state.template.bleedPadding[0] // mask位 + 出血位
            } else if (TemplateConst.LINE_DIRECTION_V === direction) {
              leftWidthPx = store.state.template.maskPadding[2] + store.state.template.bleedPadding[2]
            }
          } else if (TemplateConst.FOLDING_NAME === node.getName()) { // 摺工
            isFoldingMove = true
          } else if (node.hasName('design') && node.hasName('Line')) {
            isLine = true
            if (node.getAttr('strokeWidth') < 1 / scale) {
              isMinLine = true
            }
          }
        })
        if (isFoldingMove) { // 摺工，禁用鍵盤上下左右
          return
        }
        if (isLine) { // line
          if (isMinLine) { // 解決1px的line，小鍵盤無法移動的問題 (源碼:Util._inRange(newAttrs.height, -this.padding() * 2 - minSize, minSize))
            tr.padding(-1)
          } else {
            tr.padding(0) // 還原
          }
        }
        var trAttrs = {
          x: tr.getX(),
          y: tr.getY(),
          width: tr.getWidth(),
          height: tr.getHeight(),
          rotation: tr.rotation()
        }
        var ids = []
        var historyKeyCode = 37 // 記錄其相反的方向
        switch (keyCode) {
          case 37: // left
            if (TemplateConst.LINE_DIRECTION_H === direction) { // 橫壓線，排除左右鍵盤
              break
            } else if (TemplateConst.LINE_DIRECTION_V === direction && tr.getX() / scale < leftWidthPx) { // 豎壓線, 左邊範圍限制
              break
            }
            trAttrs.x -= TemplateConst.MOVE_STEP
            historyKeyCode = 39
            break
          case 38: // up
            if (TemplateConst.LINE_DIRECTION_V === direction) { // 豎壓線，排除上下鍵盤
              break
            } else if (TemplateConst.LINE_DIRECTION_H === direction && tr.getY() / scale < topHeightPx) { // 橫壓線，上邊範圍限制
              break
            }
            trAttrs.y -= TemplateConst.MOVE_STEP
            historyKeyCode = 40
            break
          case 39: // right
            if (TemplateConst.LINE_DIRECTION_H === direction) { // 橫壓線，排除左右鍵盤
              break
            } else if (TemplateConst.LINE_DIRECTION_V === direction && tr.getX() / scale > leftWidthPx + store.state.template.width) { // 豎壓線, 右邊範圍限制
              break
            }
            trAttrs.x += TemplateConst.MOVE_STEP
            historyKeyCode = 37
            break
          case 40: // down
            if (TemplateConst.LINE_DIRECTION_V === direction) { // 豎壓線，排除上下鍵盤
              break
            } else if (TemplateConst.LINE_DIRECTION_H === direction && tr.getY() / scale > topHeightPx + store.state.template.height) { // 橫壓線，下邊範圍限制
              break
            }
            trAttrs.y += TemplateConst.MOVE_STEP
            historyKeyCode = 38
            break
        }
        var newAttrs = getScaleAroundCenter(trAttrs, tr.getScaleX())
        newAttrs.rotation = Konva.getAngle(newAttrs.rotation) // 需要使用angle
        tr._fitNodesInto(newAttrs)
        tr.nodes().forEach(node => {
          ids.push(node.getId())
        })
        return initHistoryData(ids, 'move', { keyCode: historyKeyCode })
      }
    },
    // 添加二维码图片
    // lineQRCodImage(e) {
    //   var id = uuidv4()
    //   e.id = id
    //   e.name = 'QRcode' + id
    //   this.addFreeImage('FitScale QRcode', e)
    // }
    lineGuide(layer) { // 指導線，捕捉形狀位置(邊緣/中心点吸附，元素相互吸附)
      var _this = this
      var stage = this.$refs.stage.getStage()
      layer.on('dragmove', function(e) {
        if (TemplateConst.CRIMPLINE_NAME === e.target.getName() || TemplateConst.NEEDLELINE_NAME === e.target.getName() || TemplateConst.FOLDING_NAME === e.target.getName()) { // 壓線、針線、摺工無需指導線
          _this.lineDragmove(e)
          return
        }
        if (layer.find('.guid-line') && layer.find('.guid-line').length > 0) { // 清除所有捕捉虛線
          layer.find('.guid-line').forEach(line => line.destroy())
        }
        var lineGuideStops = stageTool.getLineGuideStops(e.target, stage, layer) // 獲取舞臺和每个設計对象的捕捉虛線
        var itemBounds = stageTool.getObjectSnappingEdges(e.target, stage) // 獲取當前對象的捕捉點
        var guides = stageTool.getGuides(lineGuideStops, itemBounds) // 獲取所有捕捉的可能性，並取出最近的快照/捕捉虛線
        if (!guides.length) { // 無捕捉點
          return
        }
        stageTool.drawGuides(guides, stage, layer) // 畫捕捉虛線
        stageTool.attractGuides(e.target, guides) // 進行位置吸附
        layer.batchDraw()
      })
      layer.on('dragend', function(e) {
        if (TemplateConst.CRIMPLINE_NAME === e.target.getName() || TemplateConst.NEEDLELINE_NAME === e.target.getName() || TemplateConst.FOLDING_NAME === e.target.getName()) { // 壓線、針線、摺工無需指導線
          _this.lineDragend(e)
          return
        }
        if (layer.find('.guid-line') && layer.find('.guid-line').length > 0) { // 清除所有捕捉虛線
          layer.find('.guid-line').forEach(line => line.destroy())
          layer.batchDraw()
        }
      })
    },
    mouseStyle(layer) { // 鼠標樣式
      var stage = this.$refs.stage.getStage()
      layer.on('mouseenter', function() {
        stage.container().style.cursor = 'move'
      })
      layer.on('mouseleave', function() {
        stage.container().style.cursor = 'default'
      })
    },
    uvChange(data) { // UV
      var konvaId = data.konvaId
      var srcIsUV = data.srcIsUV // srcIsUV: true要UV，false沒有UV
      if (konvaId && typeof (konvaId) !== 'undefined') {
        var shape = this.getKonvaNode(konvaId)
        shape.setAttr('srcIsUV', srcIsUV)
        // 更新狀態
        store.commit('template/updateEditNode', { pageId: store.state.design.pageId, dataArray: [{ 'id': shape.getId(), 'value': { srcIsUV: srcIsUV }}] })
      } else {
        const tr = this.getTransformerNode()
        if (tr.nodes().length > 0) {
          var dataArray = []
          tr.nodes().forEach(node => {
            node.setAttr('srcIsUV', srcIsUV)
            dataArray.push({ 'id': node.getId(), 'value': { srcIsUV: srcIsUV }})
          })
          // 更新狀態
          store.commit('template/updateEditNode', { pageId: store.state.design.pageId, dataArray: dataArray })
        }
      }
    },
    // 拖动事件
    dragMove(layer) {
      layer.on('dragmove', debounce((e) => {
        const trList = this.getTransformerNode()
        // 选中多个元素的时候
        if (trList.nodes().length > 1) {
          // store.commit('template/setMove', { x: trList.attrs.x, y: trList.attrs.y, width: trList.getWidth(), height: trList.getHeight() })
          store.commit('design/setMovePosition', { x: trList.attrs.x, y: trList.attrs.y })
        } else {
          if (trList.nodes()[0].getName().indexOf('group') !== -1) {
            store.commit('design/setMovePosition', { x: trList.attrs.x, y: trList.attrs.y })
          } else {
            this.movePosition(e)
          }
        }
      }), 500)
    },
    movePosition(e) {
      const scalex = e.getScaleX()
      const scaley = e.getScaleY()
      if (e.getName().indexOf('Circle') !== -1 || e.getName().indexOf('Triangle') !== -1) {
        var scale = this.$refs.stage.getStage().attrs.scaleX
        const rect = e.getClientRect()
        store.commit('design/setMovePosition', { x: e.attrs.x - (rect.width / scale / 2), y: e.attrs.y - e.getHeight() / 2, width: e.getWidth() * scalex, height: e.getHeight() * scaley })
        return
      }
      store.commit('design/setMovePosition', { x: e.attrs.x - (e.attrs.strokeWidth ? e.attrs.strokeWidth / 2 : 0), y: e.attrs.y - ((e.attrs.strokeWidth ? e.attrs.strokeWidth / 2 : 0)), width: e.getWidth() * scalex, height: e.getHeight() * scaley })
    }
  }
}
</script>
